@amritk/nish-x86_64-linux 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/threads.ts +438 -14
package/std/net/http2.ts
CHANGED
|
@@ -25,12 +25,15 @@
|
|
|
25
25
|
*
|
|
26
26
|
* **Events.** `next()` reads the frames `feed` handed it and answers the
|
|
27
27
|
* first that means something to the program: `H2_REQUEST` (a stream opened
|
|
28
|
-
* with a request's header section; `fields` holds it
|
|
29
|
-
*
|
|
30
|
-
* next
|
|
31
|
-
* it,
|
|
32
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* with a request's header section; `fields` holds it until the next header
|
|
29
|
+
* block on the connection is read, for any stream, and a program copies what
|
|
30
|
+
* it keeps past that: the next block empties `fields` and reuses the arrays
|
|
31
|
+
* it pointed into), `H2_DATA` (some of a request body, a window onto the input
|
|
32
|
+
* buffer that stays valid until the next `feed`), `H2_TRAILERS`, `H2_RESET`
|
|
33
|
+
* (the stream is gone: the peer reset it, or broke a rule that resets it —
|
|
34
|
+
* `errorCode` says which), `H2_WINDOW` (a send window that held `writeData`
|
|
35
|
+
* back has opened: `stream`, or 0 for the connection's), `H2_GOAWAY` (the
|
|
36
|
+
* peer is going away; `lastStreamId`), and
|
|
34
37
|
* `H2_ERROR`, final: the connection broke a rule, a GOAWAY with `errorCode`
|
|
35
38
|
* is queued, and every later call answers it again. `H2_NEED_MORE` asks for
|
|
36
39
|
* more input, or for the output to drain: a frame is read only while the
|
|
@@ -50,24 +53,34 @@
|
|
|
50
53
|
* (COMPRESSION_ERROR, RFC 7541 §2.3.2); DATA past the connection window, or a
|
|
51
54
|
* window pushed past 2^31 − 1 (FLOW_CONTROL_ERROR, §6.9); a setting
|
|
52
55
|
* `http2SettingError` refuses; a header block longer than `maxHeaderBlock`
|
|
53
|
-
* or cut into more than `H2_MAX_FRAGMENTS` frames,
|
|
54
|
-
* the
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* the
|
|
56
|
+
* or cut into more than `H2_MAX_FRAGMENTS` frames, a header block that would
|
|
57
|
+
* make the HPACK table copy past its budget (`HPACK_ERR_COPY_BUDGET`), and
|
|
58
|
+
* more streams reset than `resetBudget` allows — by the peer, or by the
|
|
59
|
+
* server for a stream error the peer caused (ENHANCE_YOUR_CALM, §10.5). A
|
|
60
|
+
* stream error resets one stream and the connection lives: a malformed request
|
|
61
|
+
* — any refusal of `nish/net/http-fields`, trailers without END_STREAM, a body
|
|
62
|
+
* that disagrees with `content-length` (PROTOCOL_ERROR, §8.1.1); DATA or
|
|
63
|
+
* HEADERS after the peer ended the stream (STREAM_CLOSED, §5.1); DATA past the
|
|
64
|
+
* stream's window, or its window pushed past 2^31 − 1 (FLOW_CONTROL_ERROR); a
|
|
65
|
+
* stream past `maxStreams` (REFUSED_STREAM, §5.1.2); a stream that depends on
|
|
66
|
+
* itself and a PRIORITY of the wrong length. A request whose header list is
|
|
67
|
+
* longer than `maxHeaderListSize` is answered with a 431 and never reaches the
|
|
68
|
+
* program (§10.5.1).
|
|
64
69
|
*
|
|
65
70
|
* **Caps are start-up numbers**, all in `Http2Config`: the streams open at
|
|
66
71
|
* once, the windows, the largest frame and header block accepted, the header
|
|
67
|
-
* list, the output buffer and the reset budget. Every
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
72
|
+
* list, the output buffer and the reset budget. Every stream slot is
|
|
73
|
+
* allocated by the constructor; the input, output and header-block buffers
|
|
74
|
+
* are borrowed from an `Http2Pools` when first needed and given back, zeroed
|
|
75
|
+
* as far as they were written, once each is empty or the connection ends, so
|
|
76
|
+
* an idle connection holds none. A server shares one `Http2Pools` among its
|
|
77
|
+
* slots; a connection on its own makes a private one that holds one buffer of
|
|
78
|
+
* each. Once the pools are warm, reading DATA, writing DATA and every control
|
|
79
|
+
* frame allocate nothing, and neither does a header block once the
|
|
80
|
+
* decoder's arrays have grown to the largest block and header list seen: it
|
|
81
|
+
* decodes into storage the connection owns, bounded by `maxHeaderBlock` and
|
|
82
|
+
* `maxHeaderListSize` whether the stream is opened, refused or reset
|
|
83
|
+
* (`docs/security/http2.md`, H2-1).
|
|
71
84
|
*
|
|
72
85
|
* The server sends no PUSH_PROMISE and no PRIORITY, and reads PRIORITY only to
|
|
73
86
|
* check it: RFC 9113 §5.3.2 deprecates the scheme. Every field it sends is a
|
|
@@ -80,10 +93,18 @@
|
|
|
80
93
|
* Private helpers share the importing program's flat symbol namespace
|
|
81
94
|
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
82
95
|
*
|
|
96
|
+
* **The Date.** A final response carries the `date` in `conn.date`, 29
|
|
97
|
+
* bytes of IMF-fixdate, right after `:status`, unless the program's fields
|
|
98
|
+
* hold their own; a 1xx and trailers carry none. A bare connection has no
|
|
99
|
+
* clock, so `date` starts empty and nothing is sent; `Http2TlsServer` fills
|
|
100
|
+
* it from its `HttpDate` (RFC 9110 §6.6.1).
|
|
101
|
+
*
|
|
83
102
|
* Written from RFC 9113 and RFC 8441, not ported from another implementation.
|
|
84
103
|
*/
|
|
104
|
+
import { BufferPool } from "nish/net/buffer-pool"
|
|
85
105
|
import {
|
|
86
106
|
HPACK_DEFAULT_TABLE_SIZE,
|
|
107
|
+
HPACK_ERR_COPY_BUDGET,
|
|
87
108
|
HPACK_INDEX_NEVER,
|
|
88
109
|
HPACK_INDEX_WITHOUT,
|
|
89
110
|
HPACK_LIST_TOO_LARGE,
|
|
@@ -238,10 +259,46 @@ export class Http2Config {
|
|
|
238
259
|
enableConnectProtocol: boolean = false
|
|
239
260
|
/** The output buffer, in bytes. */
|
|
240
261
|
outputSize: i32 = 65536
|
|
241
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* How many streams may be reset — by the peer, or by the server for a
|
|
264
|
+
* stream error the peer caused — net of the ones the server finishes.
|
|
265
|
+
*/
|
|
242
266
|
resetBudget: i32 = 100
|
|
243
267
|
}
|
|
244
268
|
|
|
269
|
+
/**
|
|
270
|
+
* The three buffers a connection borrows — its input, its output and the
|
|
271
|
+
* header block it gathers — as one pool each, sized from one `Http2Config`.
|
|
272
|
+
* `Http2TlsServer` owns one with a ceiling of its slot count and shares it
|
|
273
|
+
* with every slot; a bare connection makes its own, of ceiling 1, the first
|
|
274
|
+
* time it needs a buffer.
|
|
275
|
+
*/
|
|
276
|
+
export class Http2Pools {
|
|
277
|
+
/** `maxFrameSize` and a frame header: the largest frame `next` reads. */
|
|
278
|
+
input: BufferPool
|
|
279
|
+
/** `outputSize`. */
|
|
280
|
+
output: BufferPool
|
|
281
|
+
/** `maxHeaderBlock`. */
|
|
282
|
+
block: BufferPool
|
|
283
|
+
|
|
284
|
+
/** Pools of at most `ceiling` buffers each, for connections under `config`; `name` prefixes each pool's own. Allocates no buffer. */
|
|
285
|
+
constructor(name: string, config: Http2Config, ceiling: i32) {
|
|
286
|
+
this.input = new BufferPool(`${name}.input`, config.maxFrameSize + H2_FRAME_HEADER_SIZE, ceiling)
|
|
287
|
+
this.output = new BufferPool(`${name}.output`, config.outputSize, ceiling)
|
|
288
|
+
this.block = new BufferPool(`${name}.block`, config.maxHeaderBlock, ceiling)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** How many buffers of the three kinds are lent now. */
|
|
292
|
+
lent(): i32 {
|
|
293
|
+
return this.input.lent() + this.output.lent() + this.block.lent()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** How many buffers of the three kinds have ever been made. */
|
|
297
|
+
made(): i32 {
|
|
298
|
+
return this.input.made + this.output.made + this.block.made
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
245
302
|
/** One stream's slot. `id` is 0 while the slot is free. */
|
|
246
303
|
export class Http2Stream {
|
|
247
304
|
/** What the server may still send on the stream. */
|
|
@@ -302,6 +359,21 @@ const http2CheckWindow = (what: string, buf: u8[], off: i32, len: i32): void =>
|
|
|
302
359
|
}
|
|
303
360
|
}
|
|
304
361
|
|
|
362
|
+
/** Whether one of `names` is `date` in any ASCII case (RFC 9110 §5.1). */
|
|
363
|
+
const http2NamesHoldDate = (names: u8[][]): boolean => {
|
|
364
|
+
// An ASCII letter's case is its 0x20 bit, and every octet of `date` is a letter.
|
|
365
|
+
const bit: i32 = 32
|
|
366
|
+
for (const name of names) {
|
|
367
|
+
if (toI32(name.length) === 4) {
|
|
368
|
+
const d: boolean = (toI32(name[0]) | bit) === 100 && (toI32(name[1]) | bit) === 97
|
|
369
|
+
if (d && (toI32(name[2]) | bit) === 116 && (toI32(name[3]) | bit) === 101) {
|
|
370
|
+
return true
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return false
|
|
375
|
+
}
|
|
376
|
+
|
|
305
377
|
/** Panics unless `value` lies in `[low, high]`: a cap out of range is the program's mistake. */
|
|
306
378
|
const http2CheckCap = (what: string, value: i32, low: i32, high: i32): void => {
|
|
307
379
|
if (value < low || value > high) {
|
|
@@ -318,6 +390,12 @@ export class Http2Connection {
|
|
|
318
390
|
// order the layout packs without padding.
|
|
319
391
|
config: Http2Config
|
|
320
392
|
frame: Http2Frame
|
|
393
|
+
/**
|
|
394
|
+
* Where `input`, `output` and `block` are borrowed from: the server's, or
|
|
395
|
+
* a pool of one of each the connection makes itself when it first needs a
|
|
396
|
+
* buffer. A buffer the connection does not hold is `empty`.
|
|
397
|
+
*/
|
|
398
|
+
pools: Http2Pools | null = null
|
|
321
399
|
/** Bytes fed and not read yet are `input[inputStart .. inputEnd)`. */
|
|
322
400
|
input: u8[]
|
|
323
401
|
/** Bytes to send are `output[outputStart .. outputEnd)`. */
|
|
@@ -328,7 +406,11 @@ export class Http2Connection {
|
|
|
328
406
|
encoded: u8[]
|
|
329
407
|
decoder: HpackDecoder
|
|
330
408
|
encoder: HpackEncoder
|
|
331
|
-
/**
|
|
409
|
+
/**
|
|
410
|
+
* The last request's or trailers' fields, valid until the next header block
|
|
411
|
+
* on the connection, for any stream: that block empties them and reuses
|
|
412
|
+
* their arrays, so a program copies what it keeps (`docs/security/http2.md`, H2-1).
|
|
413
|
+
*/
|
|
332
414
|
fields: HttpFields
|
|
333
415
|
streams: Http2Stream[]
|
|
334
416
|
/** The identifiers the server reset most recently, a ring. */
|
|
@@ -338,8 +420,20 @@ export class Http2Connection {
|
|
|
338
420
|
/** The bytes of `:status` and of a three-digit status, reused for every response. */
|
|
339
421
|
statusName: u8[]
|
|
340
422
|
statusValue: u8[]
|
|
341
|
-
/**
|
|
342
|
-
|
|
423
|
+
/**
|
|
424
|
+
* The `date` every final response carries (RFC 9110 §6.6.1), as the 29
|
|
425
|
+
* bytes of an IMF-fixdate, or empty for none. A bare connection has no
|
|
426
|
+
* clock, so it starts empty; `Http2TlsServer` points it at its `HttpDate`'s
|
|
427
|
+
* bytes, which that rewrites in place once a second.
|
|
428
|
+
*/
|
|
429
|
+
date: u8[]
|
|
430
|
+
/** The bytes of `date`, the field's name, made once. */
|
|
431
|
+
dateName: u8[]
|
|
432
|
+
/**
|
|
433
|
+
* One empty array, made once: GOAWAY's debug data, which the server never
|
|
434
|
+
* sends, and `input`, `output` and `block` while the connection holds none.
|
|
435
|
+
*/
|
|
436
|
+
empty: u8[]
|
|
343
437
|
/** The server's SETTINGS, made once and sent by every `restart`. */
|
|
344
438
|
settingIds: i32[]
|
|
345
439
|
settingValues: i64[]
|
|
@@ -388,6 +482,10 @@ export class Http2Connection {
|
|
|
388
482
|
lastStreamId: i32 = 0
|
|
389
483
|
/** The last stream identifier the server's first GOAWAY named, which no later one may raise (§6.8). */
|
|
390
484
|
sentLast: i32 = 0
|
|
485
|
+
/** How far into `input`, `output` and `block` the connection has written since it borrowed each: what giving it back zeroes. */
|
|
486
|
+
inputUsed: i32 = 0
|
|
487
|
+
outputUsed: i32 = 0
|
|
488
|
+
blockUsed: i32 = 0
|
|
391
489
|
blockEndStream: boolean = false
|
|
392
490
|
/** Whether the peer has acknowledged the server's SETTINGS. */
|
|
393
491
|
settingsAcked: boolean = false
|
|
@@ -401,12 +499,17 @@ export class Http2Connection {
|
|
|
401
499
|
endStream: boolean = false
|
|
402
500
|
/** Whether H2_RESET came from the peer's RST_STREAM rather than a rule its stream broke. */
|
|
403
501
|
resetByPeer: boolean = false
|
|
502
|
+
/** The server's SETTINGS are not written yet: the first `holdOutput` writes them. */
|
|
503
|
+
prefaceOwed: boolean = false
|
|
404
504
|
|
|
405
505
|
/**
|
|
406
|
-
* A connection under `config`,
|
|
506
|
+
* A connection under `config`, owing its SETTINGS — and the WINDOW_UPDATE
|
|
407
507
|
* that raises the connection's window, when `connectionWindowSize` asks
|
|
408
|
-
* for one —
|
|
409
|
-
*
|
|
508
|
+
* for one — since a server's preface is its SETTINGS (§3.4): the first
|
|
509
|
+
* `wantsWrite`, `next` or response writes them into `output`. It holds no
|
|
510
|
+
* buffer until then, so `output` is empty after the constructor and after
|
|
511
|
+
* `restart`: a caller that reads `output` itself calls `wantsWrite` first.
|
|
512
|
+
* A cap outside what RFC 9113 allows panics.
|
|
410
513
|
*/
|
|
411
514
|
constructor(config: Http2Config) {
|
|
412
515
|
http2CheckCap("maxStreams", config.maxStreams, 1, 65536)
|
|
@@ -420,9 +523,10 @@ export class Http2Connection {
|
|
|
420
523
|
http2CheckCap("resetBudget", config.resetBudget, 0, H2_MAX_WINDOW)
|
|
421
524
|
this.config = config
|
|
422
525
|
this.frame = new Http2Frame()
|
|
423
|
-
this.
|
|
424
|
-
this.
|
|
425
|
-
this.
|
|
526
|
+
this.empty = []
|
|
527
|
+
this.input = this.empty
|
|
528
|
+
this.output = this.empty
|
|
529
|
+
this.block = this.empty
|
|
426
530
|
this.encoded = []
|
|
427
531
|
this.decoder = new HpackDecoder(HPACK_DEFAULT_TABLE_SIZE, config.maxHeaderListSize)
|
|
428
532
|
this.encoder = new HpackEncoder(HPACK_DEFAULT_TABLE_SIZE)
|
|
@@ -434,9 +538,10 @@ export class Http2Connection {
|
|
|
434
538
|
this.resets = new Array<i32>(config.maxStreams + H2_RECENT_RESETS)
|
|
435
539
|
this.closed = new Array<i32>(config.maxStreams + H2_RECENT_RESETS)
|
|
436
540
|
this.statusName = httpFieldBytes(":status")
|
|
437
|
-
this.noDebug = []
|
|
438
541
|
this.statusValue = [48, 48, 48]
|
|
439
|
-
this.
|
|
542
|
+
this.date = []
|
|
543
|
+
this.dateName = httpFieldBytes("date")
|
|
544
|
+
this.data = this.empty
|
|
440
545
|
this.settingIds = [
|
|
441
546
|
H2_SETTINGS_HEADER_TABLE_SIZE,
|
|
442
547
|
H2_SETTINGS_MAX_CONCURRENT_STREAMS,
|
|
@@ -460,18 +565,14 @@ export class Http2Connection {
|
|
|
460
565
|
|
|
461
566
|
/**
|
|
462
567
|
* Puts the connection back as the constructor left it, for the next peer:
|
|
463
|
-
* every stream closed, both HPACK tables empty,
|
|
464
|
-
*
|
|
465
|
-
* reuse one connection per slot (WP34 N9).
|
|
568
|
+
* every stream closed, both HPACK tables empty, every buffer given back,
|
|
569
|
+
* and the server's SETTINGS owed again. It allocates nothing, which is what
|
|
570
|
+
* lets a carrier reuse one connection per slot (WP34 N9).
|
|
466
571
|
*/
|
|
467
572
|
restart(): void {
|
|
468
573
|
const config: Http2Config = this.config
|
|
469
|
-
|
|
470
|
-
this.
|
|
471
|
-
this.outputStart = 0
|
|
472
|
-
this.outputEnd = 0
|
|
473
|
-
this.blockLength = 0
|
|
474
|
-
this.blockStream = 0
|
|
574
|
+
// Gives every buffer back and zeroes the cursors over them.
|
|
575
|
+
this.drop()
|
|
475
576
|
this.blockEndStream = false
|
|
476
577
|
this.blockError = 0
|
|
477
578
|
this.blockFragments = 0
|
|
@@ -515,18 +616,127 @@ export class Http2Connection {
|
|
|
515
616
|
const big: i64 = toI64(H2_DEFAULT_WINDOW)
|
|
516
617
|
this.localInitialWindow = toI64(config.initialWindowSize) > big ? toI64(config.initialWindowSize) : big
|
|
517
618
|
this.recvWindow = toI64(config.connectionWindowSize)
|
|
518
|
-
this.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
619
|
+
this.prefaceOwed = true
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// ---- Borrowed buffers ---------------------------------------------------------
|
|
623
|
+
|
|
624
|
+
/** The pools the connection borrows from, made at ceiling 1 the first time a bare connection asks. */
|
|
625
|
+
lender(): Http2Pools {
|
|
626
|
+
const pools: Http2Pools | null = this.pools
|
|
627
|
+
if (pools !== null) {
|
|
628
|
+
return pools
|
|
629
|
+
}
|
|
630
|
+
const own: Http2Pools = new Http2Pools("Http2Connection", this.config, 1)
|
|
631
|
+
this.pools = own
|
|
632
|
+
return own
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Borrows `input` unless the connection holds it. */
|
|
636
|
+
holdInput(): void {
|
|
637
|
+
if (this.input.length === 0) {
|
|
638
|
+
this.input = this.lender().input.take()
|
|
639
|
+
this.inputUsed = 0
|
|
522
640
|
}
|
|
523
641
|
}
|
|
524
642
|
|
|
643
|
+
/** Borrows `output` unless the connection holds it, and writes the SETTINGS it owes. */
|
|
644
|
+
holdOutput(): void {
|
|
645
|
+
if (this.output.length === 0) {
|
|
646
|
+
this.output = this.lender().output.take()
|
|
647
|
+
this.outputUsed = 0
|
|
648
|
+
}
|
|
649
|
+
if (this.prefaceOwed) {
|
|
650
|
+
this.prefaceOwed = false
|
|
651
|
+
this.wrote(http2WriteSettings(this.output, this.outputEnd, this.settingIds, this.settingValues))
|
|
652
|
+
const raise: i32 = this.config.connectionWindowSize - H2_DEFAULT_WINDOW
|
|
653
|
+
if (raise > 0) {
|
|
654
|
+
this.wrote(http2WriteWindowUpdate(this.output, this.outputEnd, H2_ZERO, raise))
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** Borrows `block` unless the connection holds it. */
|
|
660
|
+
holdBlock(): void {
|
|
661
|
+
if (this.block.length === 0) {
|
|
662
|
+
this.block = this.lender().block.take()
|
|
663
|
+
this.blockUsed = 0
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** Gives `input` back, zeroing what was written into it; ends the last H2_DATA's window. */
|
|
668
|
+
giveInput(): void {
|
|
669
|
+
if (this.input.length > 0) {
|
|
670
|
+
this.lender().input.give(this.input, this.inputUsed)
|
|
671
|
+
this.input = this.empty
|
|
672
|
+
}
|
|
673
|
+
this.inputStart = 0
|
|
674
|
+
this.inputEnd = 0
|
|
675
|
+
this.inputUsed = 0
|
|
676
|
+
this.data = this.empty
|
|
677
|
+
this.dataStart = 0
|
|
678
|
+
this.dataLength = 0
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Gives `output` back, zeroing what was written into it, sent or not. */
|
|
682
|
+
giveOutput(): void {
|
|
683
|
+
if (this.output.length > 0) {
|
|
684
|
+
this.lender().output.give(this.output, this.outputUsed)
|
|
685
|
+
this.output = this.empty
|
|
686
|
+
}
|
|
687
|
+
this.outputStart = 0
|
|
688
|
+
this.outputEnd = 0
|
|
689
|
+
this.outputUsed = 0
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** Gives `block` back, zeroing what was gathered into it. */
|
|
693
|
+
giveBlock(): void {
|
|
694
|
+
if (this.block.length > 0) {
|
|
695
|
+
this.lender().block.give(this.block, this.blockUsed)
|
|
696
|
+
this.block = this.empty
|
|
697
|
+
}
|
|
698
|
+
this.blockLength = 0
|
|
699
|
+
this.blockUsed = 0
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Gives back every buffer the connection holds and does not need: the
|
|
704
|
+
* input once all of it is read, which ends the last H2_DATA's window; the
|
|
705
|
+
* output once all of it is sent; the header block while none is open. A
|
|
706
|
+
* carrier calls it once the program is done with the last event, so a
|
|
707
|
+
* connection waiting on its peer holds no buffer.
|
|
708
|
+
*/
|
|
709
|
+
rest(): void {
|
|
710
|
+
if (this.inputEnd === this.inputStart) {
|
|
711
|
+
this.giveInput()
|
|
712
|
+
}
|
|
713
|
+
if (this.outputEnd === this.outputStart) {
|
|
714
|
+
this.giveOutput()
|
|
715
|
+
}
|
|
716
|
+
if (this.blockStream === 0) {
|
|
717
|
+
this.giveBlock()
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Ends the connection where it stands, for a carrier that closes it: every
|
|
723
|
+
* buffer is given back, zeroed, with what was unread or unsent, and the
|
|
724
|
+
* connection answers H2_ERROR until `restart`. `errorCode` is left as it was.
|
|
725
|
+
*/
|
|
726
|
+
drop(): void {
|
|
727
|
+
this.giveInput()
|
|
728
|
+
this.giveOutput()
|
|
729
|
+
this.blockStream = 0
|
|
730
|
+
this.giveBlock()
|
|
731
|
+
this.prefaceOwed = false
|
|
732
|
+
this.state = H2_STATE_FAILED
|
|
733
|
+
}
|
|
734
|
+
|
|
525
735
|
// ---- Bytes in and out ---------------------------------------------------------
|
|
526
736
|
|
|
527
737
|
/** How many bytes `feed` would take now. */
|
|
528
738
|
inputRoom(): i32 {
|
|
529
|
-
return
|
|
739
|
+
return this.config.maxFrameSize + H2_FRAME_HEADER_SIZE - (this.inputEnd - this.inputStart)
|
|
530
740
|
}
|
|
531
741
|
|
|
532
742
|
/**
|
|
@@ -547,6 +757,7 @@ export class Http2Connection {
|
|
|
547
757
|
this.input[this.inputEnd + k] = buf[off + k]
|
|
548
758
|
}
|
|
549
759
|
this.inputEnd = this.inputEnd + n
|
|
760
|
+
this.inputUsed = this.inputEnd > this.inputUsed ? this.inputEnd : this.inputUsed
|
|
550
761
|
return n
|
|
551
762
|
}
|
|
552
763
|
|
|
@@ -556,6 +767,7 @@ export class Http2Connection {
|
|
|
556
767
|
* straight into `input` from `inputEnd`, then `received` what it read.
|
|
557
768
|
*/
|
|
558
769
|
inputTail(): i32 {
|
|
770
|
+
this.holdInput()
|
|
559
771
|
this.inputEnd = http2Compact(this.input, this.inputStart, this.inputEnd)
|
|
560
772
|
this.inputStart = 0
|
|
561
773
|
return toI32(this.input.length) - this.inputEnd
|
|
@@ -567,23 +779,27 @@ export class Http2Connection {
|
|
|
567
779
|
if (n < 0 || n > room) {
|
|
568
780
|
panic(`Http2Connection.received: ${n} bytes into ${room} of room`)
|
|
569
781
|
}
|
|
782
|
+
// A carrier's read wrote them whatever the state, so they are zeroed when the input goes back.
|
|
783
|
+
this.inputUsed = this.inputEnd + n > this.inputUsed ? this.inputEnd + n : this.inputUsed
|
|
570
784
|
if (this.state !== H2_STATE_FAILED) {
|
|
571
785
|
this.inputEnd = this.inputEnd + n
|
|
572
786
|
}
|
|
573
787
|
}
|
|
574
788
|
|
|
575
|
-
/** Marks the first `n` bytes of the output sent. */
|
|
789
|
+
/** Marks the first `n` bytes of the output sent; once all of it is, the output goes back to its pool. */
|
|
576
790
|
consume(n: i32): void {
|
|
577
791
|
const pending: i32 = this.outputEnd - this.outputStart
|
|
578
792
|
this.outputStart = this.outputStart + (n < pending ? n : pending)
|
|
579
793
|
if (this.outputStart === this.outputEnd) {
|
|
580
|
-
this.
|
|
581
|
-
this.outputEnd = 0
|
|
794
|
+
this.giveOutput()
|
|
582
795
|
}
|
|
583
796
|
}
|
|
584
797
|
|
|
585
|
-
/** Whether the output holds bytes to send. */
|
|
798
|
+
/** Whether the output holds bytes to send; the SETTINGS a fresh connection owes are written first. */
|
|
586
799
|
wantsWrite(): boolean {
|
|
800
|
+
if (this.prefaceOwed) {
|
|
801
|
+
this.holdOutput()
|
|
802
|
+
}
|
|
587
803
|
return this.outputEnd > this.outputStart
|
|
588
804
|
}
|
|
589
805
|
|
|
@@ -611,6 +827,7 @@ export class Http2Connection {
|
|
|
611
827
|
|
|
612
828
|
/** The room left at the end of the output, once what was sent is moved out of the way. */
|
|
613
829
|
room(): i32 {
|
|
830
|
+
this.holdOutput()
|
|
614
831
|
this.outputEnd = http2Compact(this.output, this.outputStart, this.outputEnd)
|
|
615
832
|
this.outputStart = 0
|
|
616
833
|
return toI32(this.output.length) - this.outputEnd
|
|
@@ -626,6 +843,7 @@ export class Http2Connection {
|
|
|
626
843
|
wrote(end: i32): boolean {
|
|
627
844
|
if (end >= 0) {
|
|
628
845
|
this.outputEnd = end
|
|
846
|
+
this.outputUsed = end > this.outputUsed ? end : this.outputUsed
|
|
629
847
|
}
|
|
630
848
|
return end >= 0
|
|
631
849
|
}
|
|
@@ -725,10 +943,30 @@ export class Http2Connection {
|
|
|
725
943
|
return true
|
|
726
944
|
}
|
|
727
945
|
|
|
728
|
-
/**
|
|
946
|
+
/**
|
|
947
|
+
* Charges one stream the peer had reset to the reset budget, whichever side
|
|
948
|
+
* sent the RST_STREAM: a client that cannot send one itself provokes the
|
|
949
|
+
* server into it (MadeYouReset, CVE-2025-8671). Answers H2_NEED_MORE, or
|
|
950
|
+
* once the budget is spent the connection error ENHANCE_YOUR_CALM (§10.5).
|
|
951
|
+
*/
|
|
952
|
+
chargeReset(): i32 {
|
|
953
|
+
this.resetCredit = this.resetCredit - 1
|
|
954
|
+
return this.resetCredit < 0 ? this.fail(H2_ENHANCE_YOUR_CALM) : H2_NEED_MORE
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** A stream error the peer caused on `id`, which the program never saw: resets it and charges the budget. */
|
|
958
|
+
refuseStream(id: i32, code: i32): i32 {
|
|
959
|
+
this.resetStream(id, code)
|
|
960
|
+
return this.chargeReset()
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** A stream error on `s`: resets it, charges the budget, and answers the H2_RESET that tells the program. */
|
|
729
964
|
streamError(s: Http2Stream, code: i32): i32 {
|
|
730
965
|
const id: i32 = s.id
|
|
731
|
-
this.
|
|
966
|
+
const charged: i32 = this.refuseStream(id, code)
|
|
967
|
+
if (charged !== H2_NEED_MORE) {
|
|
968
|
+
return charged
|
|
969
|
+
}
|
|
732
970
|
this.stream = id
|
|
733
971
|
this.errorCode = toI64(code)
|
|
734
972
|
this.resetByPeer = false
|
|
@@ -744,7 +982,7 @@ export class Http2Connection {
|
|
|
744
982
|
const last: i32 = this.goawaySent ? this.sentLast : this.lastPeerStream
|
|
745
983
|
if (
|
|
746
984
|
!this.wrote(
|
|
747
|
-
http2WriteGoaway(this.output, this.outputEnd, last, toI64(code), this.
|
|
985
|
+
http2WriteGoaway(this.output, this.outputEnd, last, toI64(code), this.empty, H2_ZERO, H2_ZERO)
|
|
748
986
|
)
|
|
749
987
|
) {
|
|
750
988
|
return false
|
|
@@ -909,10 +1147,7 @@ export class Http2Connection {
|
|
|
909
1147
|
this.recvWindow = this.recvWindow - toI64(size)
|
|
910
1148
|
if (s === null) {
|
|
911
1149
|
this.credit(null, size)
|
|
912
|
-
|
|
913
|
-
this.resetStream(id, H2_STREAM_CLOSED)
|
|
914
|
-
}
|
|
915
|
-
return H2_NEED_MORE
|
|
1150
|
+
return this.discarded(id) ? H2_NEED_MORE : this.refuseStream(id, H2_STREAM_CLOSED)
|
|
916
1151
|
}
|
|
917
1152
|
const end: boolean = f.has(H2_FLAG_END_STREAM)
|
|
918
1153
|
const received: i64 = s.received + toI64(f.contentLength)
|
|
@@ -947,6 +1182,7 @@ export class Http2Connection {
|
|
|
947
1182
|
|
|
948
1183
|
/** Appends a fragment of a header block; a block past `maxHeaderBlock` or `H2_MAX_FRAGMENTS` ends the connection. */
|
|
949
1184
|
gather(f: Http2Frame): boolean {
|
|
1185
|
+
this.holdBlock()
|
|
950
1186
|
this.blockFragments = this.blockFragments + 1
|
|
951
1187
|
if (
|
|
952
1188
|
this.blockFragments > H2_MAX_FRAGMENTS ||
|
|
@@ -958,6 +1194,7 @@ export class Http2Connection {
|
|
|
958
1194
|
this.block[this.blockLength + k] = this.input[f.contentStart + k]
|
|
959
1195
|
}
|
|
960
1196
|
this.blockLength = this.blockLength + f.contentLength
|
|
1197
|
+
this.blockUsed = this.blockLength > this.blockUsed ? this.blockLength : this.blockUsed
|
|
961
1198
|
return true
|
|
962
1199
|
}
|
|
963
1200
|
|
|
@@ -990,9 +1227,15 @@ export class Http2Connection {
|
|
|
990
1227
|
endBlock(): i32 {
|
|
991
1228
|
const id: i32 = this.blockStream
|
|
992
1229
|
this.blockStream = 0
|
|
1230
|
+
// The decoder takes back the arrays the last request's fields point into,
|
|
1231
|
+
// so those fields go first: a stale read finds nothing, never this block's octets.
|
|
1232
|
+
this.fields.clear()
|
|
993
1233
|
const decoded: i32 = this.decoder.decode(this.block, H2_ZERO, this.blockLength)
|
|
1234
|
+
// The decoder copied every field out, so the block goes back before the request is read.
|
|
1235
|
+
this.giveBlock()
|
|
994
1236
|
if (decoded < 0) {
|
|
995
|
-
|
|
1237
|
+
// A block past the decoder's copy budget is valid HPACK used as a lever, not a broken one (§10.5).
|
|
1238
|
+
return this.fail(decoded === HPACK_ERR_COPY_BUDGET ? H2_ENHANCE_YOUR_CALM : H2_COMPRESSION_ERROR)
|
|
996
1239
|
}
|
|
997
1240
|
const s: Http2Stream | null = this.find(id)
|
|
998
1241
|
if (s !== null) {
|
|
@@ -1012,16 +1255,14 @@ export class Http2Connection {
|
|
|
1012
1255
|
return H2_NEED_MORE
|
|
1013
1256
|
}
|
|
1014
1257
|
if (this.blockError !== 0) {
|
|
1015
|
-
this.
|
|
1016
|
-
return H2_NEED_MORE
|
|
1258
|
+
return this.refuseStream(id, this.blockError)
|
|
1017
1259
|
}
|
|
1018
1260
|
if (decoded === HPACK_LIST_TOO_LARGE) {
|
|
1019
1261
|
this.tooLarge(id)
|
|
1020
|
-
return
|
|
1262
|
+
return this.chargeReset()
|
|
1021
1263
|
}
|
|
1022
1264
|
if (this.active >= this.config.maxStreams) {
|
|
1023
|
-
this.
|
|
1024
|
-
return H2_NEED_MORE
|
|
1265
|
+
return this.refuseStream(id, H2_REFUSED_STREAM)
|
|
1025
1266
|
}
|
|
1026
1267
|
const shape: i32 = this.fields.readRequest(
|
|
1027
1268
|
this.decoder.names,
|
|
@@ -1029,8 +1270,7 @@ export class Http2Connection {
|
|
|
1029
1270
|
this.config.enableConnectProtocol
|
|
1030
1271
|
)
|
|
1031
1272
|
if (shape !== HTTP_FIELDS_OK || (this.blockEndStream && this.fields.contentLength > toI64(0))) {
|
|
1032
|
-
this.
|
|
1033
|
-
return H2_NEED_MORE
|
|
1273
|
+
return this.refuseStream(id, H2_PROTOCOL_ERROR)
|
|
1034
1274
|
}
|
|
1035
1275
|
const opened: Http2Stream = this.open(id)
|
|
1036
1276
|
opened.contentLength = this.fields.contentLength
|
|
@@ -1070,7 +1310,8 @@ export class Http2Connection {
|
|
|
1070
1310
|
* sending (§8.1), so the stream never reaches the program.
|
|
1071
1311
|
*/
|
|
1072
1312
|
tooLarge(id: i32): void {
|
|
1073
|
-
this.
|
|
1313
|
+
// The control reserve holds this with its date: under fifty bytes.
|
|
1314
|
+
this.encodeStatus(H2_STATUS_TOO_LARGE, true)
|
|
1074
1315
|
const n: i32 = toI32(this.encoded.length)
|
|
1075
1316
|
this.wrote(
|
|
1076
1317
|
http2WriteHeaders(
|
|
@@ -1089,8 +1330,14 @@ export class Http2Connection {
|
|
|
1089
1330
|
}
|
|
1090
1331
|
}
|
|
1091
1332
|
|
|
1092
|
-
/**
|
|
1093
|
-
|
|
1333
|
+
/**
|
|
1334
|
+
* Empties `encoded` and starts a block with `:status`, or with nothing for
|
|
1335
|
+
* 0 (trailers), then with `date` when `withDate` and the status is final.
|
|
1336
|
+
* Both are literals without indexing, so the dynamic table never moves:
|
|
1337
|
+
* `date` is the static table's name 33 and a value of 29 bytes, in Huffman
|
|
1338
|
+
* when that is shorter.
|
|
1339
|
+
*/
|
|
1340
|
+
encodeStatus(status: i32, withDate: boolean): void {
|
|
1094
1341
|
while (toI32(this.encoded.length) > 0) {
|
|
1095
1342
|
this.encoded.pop()
|
|
1096
1343
|
}
|
|
@@ -1100,6 +1347,12 @@ export class Http2Connection {
|
|
|
1100
1347
|
this.statusValue[2] = toU8((status % 10) + H2_DIGIT_ZERO)
|
|
1101
1348
|
this.encoder.encodeField(this.encoded, this.statusName, this.statusValue, HPACK_INDEX_WITHOUT, false)
|
|
1102
1349
|
}
|
|
1350
|
+
if (withDate && status >= 200 && toI32(this.date.length) > 0) {
|
|
1351
|
+
const huffman: boolean =
|
|
1352
|
+
hpackHuffmanLength(this.encoder.huffman, this.date, H2_ZERO, toI32(this.date.length)) <
|
|
1353
|
+
toI32(this.date.length)
|
|
1354
|
+
this.encoder.encodeField(this.encoded, this.dateName, this.date, HPACK_INDEX_WITHOUT, huffman)
|
|
1355
|
+
}
|
|
1103
1356
|
}
|
|
1104
1357
|
|
|
1105
1358
|
/** PRIORITY (§6.3): checked and otherwise ignored, and it opens nothing. */
|
|
@@ -1111,8 +1364,7 @@ export class Http2Connection {
|
|
|
1111
1364
|
if (s !== null) {
|
|
1112
1365
|
return this.streamError(s, f.streamError)
|
|
1113
1366
|
}
|
|
1114
|
-
this.
|
|
1115
|
-
return H2_NEED_MORE
|
|
1367
|
+
return this.refuseStream(f.streamId, f.streamError)
|
|
1116
1368
|
}
|
|
1117
1369
|
|
|
1118
1370
|
/** RST_STREAM (§6.4): the stream is closed; on an idle one it is a connection error. */
|
|
@@ -1125,9 +1377,9 @@ export class Http2Connection {
|
|
|
1125
1377
|
return H2_NEED_MORE
|
|
1126
1378
|
}
|
|
1127
1379
|
this.release(s)
|
|
1128
|
-
|
|
1129
|
-
if (
|
|
1130
|
-
return
|
|
1380
|
+
const charged: i32 = this.chargeReset()
|
|
1381
|
+
if (charged !== H2_NEED_MORE) {
|
|
1382
|
+
return charged
|
|
1131
1383
|
}
|
|
1132
1384
|
this.stream = f.streamId
|
|
1133
1385
|
this.errorCode = f.errorCode
|
|
@@ -1263,12 +1515,18 @@ export class Http2Connection {
|
|
|
1263
1515
|
for (let k: i32 = 0; k < toI32(names.length) && k < toI32(values.length); k++) {
|
|
1264
1516
|
bound = bound + toI64(names[k].length) + toI64(values[k].length) + toI64(11)
|
|
1265
1517
|
}
|
|
1518
|
+
// The server's `date` goes on a final response unless the program sent
|
|
1519
|
+
// its own, so a response never carries two.
|
|
1520
|
+
const withDate: boolean = status >= 200 && toI32(this.date.length) > 0 && !http2NamesHoldDate(names)
|
|
1521
|
+
if (withDate) {
|
|
1522
|
+
bound = bound + toI64(this.dateName.length) + toI64(this.date.length) + toI64(11)
|
|
1523
|
+
}
|
|
1266
1524
|
const frames: i64 = bound / toI64(this.peerMaxFrameSize) + toI64(1)
|
|
1267
1525
|
const need: i64 = bound + frames * toI64(H2_FRAME_HEADER_SIZE)
|
|
1268
1526
|
if (need > toI64(this.bodyRoom())) {
|
|
1269
1527
|
return toI64(this.output.length) < need + toI64(H2_CONTROL_RESERVE) ? H2_INVALID : H2_AGAIN
|
|
1270
1528
|
}
|
|
1271
|
-
this.encodeStatus(status)
|
|
1529
|
+
this.encodeStatus(status, withDate)
|
|
1272
1530
|
for (let k: i32 = 0; k < toI32(names.length) && k < toI32(values.length); k++) {
|
|
1273
1531
|
const value: u8[] = values[k]
|
|
1274
1532
|
// One choice covers the name and the value, so Huffman is taken only
|