@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.
Files changed (59) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +215 -18
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +4 -4
  14. package/runtime/runtime-wasm.c +5 -3
  15. package/runtime/runtime.c +69 -13
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +223 -22
  18. package/std/README.md +81 -51
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +932 -61
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1120 -34
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +211 -31
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-listener.ts +88 -7
  45. package/std/net/quic-packet.ts +243 -82
  46. package/std/net/quic-recovery.ts +361 -30
  47. package/std/net/quic-stream.ts +424 -24
  48. package/std/net/quic.ts +519 -167
  49. package/std/net/router.ts +676 -0
  50. package/std/net/tls/codec.ts +426 -27
  51. package/std/net/tls/record-server.ts +478 -132
  52. package/std/net/tls/record.ts +133 -42
  53. package/std/net/tls/schedule.ts +464 -55
  54. package/std/net/tls/ticket.ts +610 -0
  55. package/std/net/tls-tcp.ts +196 -53
  56. package/std/net/tls.ts +1018 -260
  57. package/std/net/url.ts +571 -0
  58. package/std/random.ts +94 -0
  59. 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), `H2_DATA` (some of a
29
- * request body, a window onto the input buffer that stays valid until the
30
- * next `feed`), `H2_TRAILERS`, `H2_RESET` (the stream is gone: the peer reset
31
- * it, or broke a rule that resets it — `errorCode` says which), `H2_WINDOW`
32
- * (a send window that held `writeData` back has opened: `stream`, or 0 for the
33
- * connection's), `H2_GOAWAY` (the peer is going away; `lastStreamId`), and
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, and more streams reset by
54
- * the peer than `resetBudget` allows (ENHANCE_YOUR_CALM, §10.5). A stream
55
- * error resets one stream and the connection lives: a malformed request — any
56
- * refusal of `nish/net/http-fields`, trailers without END_STREAM, a body that
57
- * disagrees with `content-length` (PROTOCOL_ERROR, §8.1.1); DATA or HEADERS
58
- * after the peer ended the stream (STREAM_CLOSED, §5.1); DATA past the
59
- * stream's window, or its window pushed past 2^31 − 1 (FLOW_CONTROL_ERROR);
60
- * a stream past `maxStreams` (REFUSED_STREAM, §5.1.2); a stream that depends
61
- * on itself and a PRIORITY of the wrong length. A request whose header list
62
- * is longer than `maxHeaderListSize` is answered with a 431 and never reaches
63
- * the program (§10.5.1).
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 buffer and every
68
- * stream slot is allocated by the constructor; reading DATA, writing DATA and
69
- * every control frame allocate nothing. A header block does allocate, through
70
- * `HpackDecoder` (`docs/security/http2.md`, H2-1).
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
- /** How many streams the peer may reset before finishing, net of the ones it lets finish. */
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
- /** The last request's or trailers' fields. */
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
- /** GOAWAY's debug data, which the server never sends: one empty array, made once. */
342
- noDebug: u8[]
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`, with its SETTINGS — and the WINDOW_UPDATE
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 — already in `output`, since a server's preface is its SETTINGS
409
- * (§3.4). A cap outside what RFC 9113 allows panics.
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.input = new Array<u8>(config.maxFrameSize + H2_FRAME_HEADER_SIZE)
424
- this.output = new Array<u8>(config.outputSize)
425
- this.block = new Array<u8>(config.maxHeaderBlock)
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.data = this.input
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, and the server's SETTINGS
464
- * in `output` again. It allocates nothing, which is what lets a carrier
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
- this.inputStart = 0
470
- this.inputEnd = 0
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.outputEnd = http2WriteSettings(this.output, H2_ZERO, this.settingIds, this.settingValues)
519
- const raise: i32 = config.connectionWindowSize - H2_DEFAULT_WINDOW
520
- if (raise > 0) {
521
- this.outputEnd = http2WriteWindowUpdate(this.output, this.outputEnd, H2_ZERO, raise)
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 toI32(this.input.length) - (this.inputEnd - this.inputStart)
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.outputStart = 0
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
- /** A stream error on `s`: resets it, and answers the H2_RESET that tells the program. */
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.resetStream(id, code)
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.noDebug, H2_ZERO, H2_ZERO)
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
- if (!this.discarded(id)) {
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
- return this.fail(H2_COMPRESSION_ERROR)
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.resetStream(id, this.blockError)
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 H2_NEED_MORE
1262
+ return this.chargeReset()
1021
1263
  }
1022
1264
  if (this.active >= this.config.maxStreams) {
1023
- this.resetStream(id, H2_REFUSED_STREAM)
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.resetStream(id, H2_PROTOCOL_ERROR)
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.encodeStatus(H2_STATUS_TOO_LARGE)
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
- /** Empties `encoded` and starts a block with `:status`, or with nothing for 0 (trailers). */
1093
- encodeStatus(status: i32): void {
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.resetStream(f.streamId, f.streamError)
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
- this.resetCredit = this.resetCredit - 1
1129
- if (this.resetCredit < 0) {
1130
- return this.fail(H2_ENHANCE_YOUR_CALM)
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