@amritk/nish-x86_64-linux 0.20.0 → 0.22.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 (74) hide show
  1. package/INSTALL.md +52 -10
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2777 -0
  4. package/package.json +2 -1
  5. package/runtime/browser.d.ts +6 -0
  6. package/runtime/browser.mjs +77 -0
  7. package/runtime/core/arrays.d.ts +36 -0
  8. package/runtime/core/arrays.mjs +153 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +80 -0
  11. package/runtime/core/host.mjs +325 -0
  12. package/runtime/core/numbers.d.ts +66 -0
  13. package/runtime/core/numbers.mjs +343 -0
  14. package/runtime/core/result.d.ts +45 -0
  15. package/runtime/core/result.mjs +140 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +46 -2
  22. package/runtime/nish.h +75 -2
  23. package/runtime/nish.mjs +23 -3
  24. package/runtime/node.mjs +386 -0
  25. package/runtime/runtime-bytes.c +401 -0
  26. package/runtime/runtime-cross.c +1 -1
  27. package/runtime/runtime-host.c +1 -1
  28. package/runtime/runtime-net.c +12 -1
  29. package/runtime/runtime-os.c +1 -1
  30. package/runtime/runtime-parallel.c +1 -1
  31. package/runtime/runtime-simd.c +231 -7
  32. package/runtime/runtime-term.c +504 -0
  33. package/runtime/runtime-wasm.c +0 -11
  34. package/runtime/runtime.c +190 -6
  35. package/runtime/shim.mjs +6 -985
  36. package/runtime/sqlite/LICENSE.md +85 -0
  37. package/runtime/sqlite/README.md +45 -0
  38. package/runtime/sqlite/nish-sqlite.c +359 -0
  39. package/runtime/sqlite/sqlite3.c +271131 -0
  40. package/runtime/sqlite/sqlite3.h +14447 -0
  41. package/scripts/build.sh +311 -59
  42. package/std/LICENSE-fast-float +23 -0
  43. package/std/LICENSE-fdlibm +8 -0
  44. package/std/README.md +32 -12
  45. package/std/base64.ts +116 -0
  46. package/std/bytes.ts +256 -0
  47. package/std/cli.ts +344 -0
  48. package/std/collections.ts +6 -5
  49. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  50. package/std/crypto/base64url.ts +125 -0
  51. package/std/crypto/bcrypt.ts +1557 -0
  52. package/std/crypto/hmac.ts +138 -2
  53. package/std/crypto/x509.ts +11 -122
  54. package/std/json-tokenizer.ts +2998 -57
  55. package/std/json-value.ts +22 -3
  56. package/std/json.ts +87 -8
  57. package/std/math.ts +608 -0
  58. package/std/net/http1-server.ts +282 -40
  59. package/std/net/http1.ts +40 -21
  60. package/std/net/http3-server.ts +50 -12
  61. package/std/net/postgres.ts +2294 -0
  62. package/std/net/prefork.ts +23 -3
  63. package/std/net/quic-listener.ts +20 -4
  64. package/std/random.ts +66 -4
  65. package/std/regex.ts +4274 -0
  66. package/std/sqlite.ts +391 -0
  67. package/std/term-io.ts +978 -0
  68. package/std/term-pixels.ts +874 -0
  69. package/std/term-probe.ts +459 -0
  70. package/std/term-rate.ts +993 -0
  71. package/std/term.ts +2453 -0
  72. package/std/text.ts +90 -7
  73. package/std/yaml-lines.ts +141 -0
  74. package/std/yaml.ts +4675 -0
@@ -88,7 +88,8 @@
88
88
  *
89
89
  * **Caps are start-up numbers**, all in `Http1Config`: the request target,
90
90
  * the header section and its field count, a request body, the chunk handed
91
- * over, the output buffer, a WebSocket message and the idle timeout; the
91
+ * over, the output buffer, a WebSocket message, the idle timeout and the
92
+ * deadline for a whole request head (`headTimeout`, against slowloris); the
92
93
  * slots are the pool's size. `Http1Server` builds a slot's connection on the
93
94
  * first accept into it and keeps it, so a pool sized for its peak costs at
94
95
  * start-up only what does not depend on the size, and holds at most `size`
@@ -112,6 +113,7 @@
112
113
  * implementation.
113
114
  */
114
115
  import { Secret } from "nish:secret"
116
+ import { copyBytes } from "nish/bytes"
115
117
  import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
116
118
  import {
117
119
  HTTP1_BODY,
@@ -119,6 +121,8 @@ import {
119
121
  HTTP1_CLOSED,
120
122
  HTTP1_END,
121
123
  HTTP1_ERROR,
124
+ HTTP1_FIRST_HEAD,
125
+ HTTP1_FIRST_INPUT,
122
126
  HTTP1_HEAD,
123
127
  HTTP1_NO_BODY,
124
128
  HTTP1_REFUSED,
@@ -239,6 +243,11 @@ const http1ServerCheckWindow = (what: string, buf: u8[], off: i32, len: i32): vo
239
243
 
240
244
  /** Copies `src[off .. off + n)` to `out` at `at`, which the caller has made sure holds it, and answers where it ends. */
241
245
  const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 => {
246
+ if (n > 0 && at >= 0 && off >= 0 && at <= toI32(out.length) - n && off <= toI32(src.length) - n) {
247
+ // Both windows fit, as they do for every caller: one move, not a byte loop.
248
+ copyBytes(out, at, src, off, n)
249
+ return at + n
250
+ }
242
251
  for (let k: i32 = 0; k < n && at + k < toI32(out.length) && off + k < toI32(src.length); k++) {
243
252
  if (at + k >= 0 && off + k >= 0) {
244
253
  out[at + k] = src[off + k]
@@ -344,6 +353,22 @@ export class Http1Config {
344
353
  maxMessage: i32 = 1048576
345
354
  /** Milliseconds without a byte read or written after which `expire` closes a slot. */
346
355
  idleTimeout: i32 = 60000
356
+ /**
357
+ * Milliseconds a request head may take, from its first byte to its blank
358
+ * line, before `expire` closes the slot however steadily its bytes come. The
359
+ * idle timeout alone restarts on every byte, so a client that sends one byte
360
+ * of its head each `idleTimeout` would hold its slot for good (slowloris).
361
+ */
362
+ headTimeout: i32 = 30000
363
+ /**
364
+ * Lend a slot's input and head buffers back to the server between
365
+ * exchanges: once a response is sent and no byte of the next request has
366
+ * come, the slot gives both to pools the server's slots share, and takes
367
+ * them again with the next byte. The memory they hold then follows the
368
+ * connections mid-request rather than every keep-alive connection open,
369
+ * for two pool operations a request. Off by default.
370
+ */
371
+ idleInput: boolean = false
347
372
  }
348
373
 
349
374
  /** The reason phrase a status is sent with; RFC 9112 §4 lets a client ignore it. */
@@ -462,8 +487,12 @@ export class Http1Connection {
462
487
  date: HttpDate
463
488
  /** The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is the last `H1_REQUEST`'s. */
464
489
  parser: Http1Parser
465
- /** The frame decoder, once the slot has switched to WebSocket. */
466
- ws: WsDecoder
490
+ /**
491
+ * The frame decoder: made when the slot first switches to WebSocket, so a
492
+ * slot that only ever speaks HTTP never holds its buffers, then kept for
493
+ * the slot's later peers.
494
+ */
495
+ ws: WsDecoder | null
467
496
  /**
468
497
  * Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
469
498
  * connection first needs room to write: then its own buffer, made once, or
@@ -474,14 +503,19 @@ export class Http1Connection {
474
503
  noOutput: u8[]
475
504
  /** Where `output` is borrowed from, for a connection of a pool; null for one that keeps its own. */
476
505
  pool: Http1OutputPool | null
506
+ /** Where the parser's input and head buffers go while the slot rests, with `Http1Config.idleInput`; null otherwise. */
507
+ inputs: Http1OutputPool | null
508
+ heads: Http1OutputPool | null
477
509
  /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
478
510
  data: u8[]
479
- /** A ping's payload, kept until its pong fits. */
511
+ /** A ping's payload, kept until its pong fits: empty until the first ping. */
480
512
  pong: u8[]
481
513
  /** No fields, for the responses the connection writes itself: made once. */
482
514
  noFields: u8[][]
483
515
  /** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
484
516
  lastActive: i64 = 0
517
+ /** When the head being read began to arrive, in `monotonicNanos`; meaningful while `headTimed`. */
518
+ headSince: i64 = 0
485
519
  outputStart: i32 = 0
486
520
  outputEnd: i32 = 0
487
521
  dataStart: i32 = 0
@@ -517,6 +551,8 @@ export class Http1Connection {
517
551
  pongPending: boolean = false
518
552
  /** The peer's byte stream has ended. */
519
553
  inputEnded: boolean = false
554
+ /** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
555
+ headTimed: boolean = false
520
556
 
521
557
  /** A connection under `config`. A cap outside what the connection can work with panics. */
522
558
  constructor(config: Http1Config) {
@@ -532,6 +568,7 @@ export class Http1Connection {
532
568
  http1ServerCheckCap("outputSize", config.outputSize, output, big)
533
569
  http1ServerCheckCap("maxMessage", config.maxMessage, H1_ZERO, big - H1_FRAME_HEADER)
534
570
  http1ServerCheckCap("idleTimeout", config.idleTimeout, one, big)
571
+ http1ServerCheckCap("headTimeout", config.headTimeout, one, big)
535
572
  // The buffers the peer's bytes fill grow by doubling and stop at 2^30
536
573
  // (`Http1Parser`'s input and head, `WsDecoder`'s input), so a cap whose
537
574
  // buffer could need more is refused here rather than reached by a peer:
@@ -548,12 +585,14 @@ export class Http1Connection {
548
585
  this.parser = new Http1Parser(config.maxTarget, config.maxHeaderBytes, config.maxHeaders, config.maxBody)
549
586
  this.parser.keepText = false
550
587
  this.parser.maxChunk = config.chunkSize
551
- this.ws = new WsDecoder(true, config.maxMessage)
588
+ this.ws = null
552
589
  this.noOutput = []
553
590
  this.output = this.noOutput
554
591
  this.pool = null
592
+ this.inputs = null
593
+ this.heads = null
555
594
  this.data = this.noOutput
556
- this.pong = new Array<u8>(WS_MAX_CONTROL)
595
+ this.pong = this.noOutput
557
596
  this.noFields = []
558
597
  // A line must fit whole before the parser can read it, so the buffer
559
598
  // holds the longest request line or field line however small a chunk is.
@@ -571,7 +610,10 @@ export class Http1Connection {
571
610
  /** Puts the connection back as the constructor left it, for the next peer. It allocates nothing. */
572
611
  restart(): void {
573
612
  this.parser.restart()
574
- this.ws.reset()
613
+ const ws: WsDecoder | null = this.ws
614
+ if (ws !== null) {
615
+ ws.reset()
616
+ }
575
617
  this.outputStart = 0
576
618
  this.outputEnd = 0
577
619
  this.data = this.noOutput
@@ -592,7 +634,52 @@ export class Http1Connection {
592
634
  this.writeHeld = false
593
635
  this.pongPending = false
594
636
  this.inputEnded = false
637
+ this.headTimed = false
638
+ this.headSince = 0
595
639
  this.release()
640
+ this.park()
641
+ }
642
+
643
+ /**
644
+ * Gives the parser's input and head buffers to `inputs` and `heads` while
645
+ * the connection rests between exchanges: speaking HTTP, no request open,
646
+ * nothing fed and unread. Does nothing without the pools or otherwise.
647
+ */
648
+ park(): void {
649
+ const inputs: Http1OutputPool | null = this.inputs
650
+ const heads: Http1OutputPool | null = this.heads
651
+ const p: Http1Parser = this.parser
652
+ if (inputs === null || heads === null || this.state !== H1_S_HTTP || this.requestOpen || !p.resting()) {
653
+ return
654
+ }
655
+ if (toI32(p.buf.length) > 0) {
656
+ inputs.give(p.buf)
657
+ p.buf = this.noOutput
658
+ p.body = this.noOutput
659
+ p.start = 0
660
+ p.end = 0
661
+ p.scan = 0
662
+ p.lineStart = 0
663
+ p.lineEnd = 0
664
+ }
665
+ if (toI32(p.head.length) > 0) {
666
+ heads.give(p.head)
667
+ p.head = this.noOutput
668
+ }
669
+ }
670
+
671
+ /** Takes back the buffers `park` gave away, before bytes are fed. */
672
+ unpark(): void {
673
+ const inputs: Http1OutputPool | null = this.inputs
674
+ const heads: Http1OutputPool | null = this.heads
675
+ const p: Http1Parser = this.parser
676
+ if (inputs !== null && toI32(p.buf.length) === 0) {
677
+ p.buf = inputs.take()
678
+ p.body = p.buf
679
+ }
680
+ if (heads !== null && toI32(p.head.length) === 0) {
681
+ p.head = heads.take()
682
+ }
596
683
  }
597
684
 
598
685
  // ---- Bytes in and out ---------------------------------------------------------
@@ -607,7 +694,9 @@ export class Http1Connection {
607
694
  if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
608
695
  return 0
609
696
  }
610
- const held: i32 = this.state === H1_S_WEBSOCKET ? this.ws.end - this.ws.start : this.parser.buffered()
697
+ const ws: WsDecoder | null = this.ws
698
+ const held: i32 =
699
+ this.state === H1_S_WEBSOCKET && ws !== null ? ws.end - ws.start : this.parser.buffered()
611
700
  const room: i32 = this.inputLimit() - held
612
701
  return room > 0 ? room : 0
613
702
  }
@@ -625,8 +714,9 @@ export class Http1Connection {
625
714
  return 0
626
715
  }
627
716
  if (this.state === H1_S_WEBSOCKET) {
628
- this.ws.feed(buf, off, n)
717
+ this.decoder().feed(buf, off, n)
629
718
  } else {
719
+ this.unpark()
630
720
  this.parser.feed(buf, off, n)
631
721
  }
632
722
  return n
@@ -712,12 +802,8 @@ export class Http1Connection {
712
802
  const output: u8[] = this.output
713
803
  const live: i32 = this.outputEnd - this.outputStart
714
804
  if (this.outputStart > 0) {
715
- for (
716
- let k: i32 = 0;
717
- k < live && k < toI32(output.length) && this.outputStart + k < toI32(output.length);
718
- k++
719
- ) {
720
- output[k] = output[this.outputStart + k]
805
+ if (live > 0 && this.outputStart <= toI32(output.length) - live) {
806
+ copyBytes(output, 0, output, this.outputStart, live)
721
807
  }
722
808
  this.outputStart = 0
723
809
  this.outputEnd = live
@@ -769,8 +855,9 @@ export class Http1Connection {
769
855
  this.response = H1_R_NONE
770
856
  if (this.upgraded) {
771
857
  const p: Http1Parser = this.parser
772
- this.ws.reset()
773
- this.ws.feed(p.buf, p.start, p.buffered())
858
+ const ws: WsDecoder = this.decoder()
859
+ ws.reset()
860
+ ws.feed(p.buf, p.start, p.buffered())
774
861
  this.state = H1_S_WEBSOCKET
775
862
  return this.nextFrame()
776
863
  }
@@ -778,9 +865,16 @@ export class Http1Connection {
778
865
  this.state = H1_S_CLOSING
779
866
  return H1_NEED_MORE
780
867
  }
868
+ // A pipelined head that waited behind the exchange starts its clock
869
+ // now that it is the one being read. The carrier stamped `lastActive`
870
+ // when it last moved a byte, which is no earlier than these bytes came.
871
+ this.headArrived(this.lastActive)
781
872
  }
782
873
  const event: i32 = this.parser.next()
783
874
  if (event === HTTP1_HEAD) {
875
+ // The head is whole, so its deadline is met; the next head's starts
876
+ // when its first byte is fed (`headArrived`).
877
+ this.headTimed = false
784
878
  this.requestOpen = true
785
879
  this.response = H1_R_NONE
786
880
  this.closeAfter = !this.parser.keepAlive
@@ -845,35 +939,50 @@ export class Http1Connection {
845
939
  return H1_ERROR
846
940
  }
847
941
 
942
+ /** The frame decoder, made on the slot's first switch to WebSocket. */
943
+ decoder(): WsDecoder {
944
+ const ws: WsDecoder | null = this.ws
945
+ if (ws !== null) {
946
+ return ws
947
+ }
948
+ const made: WsDecoder = new WsDecoder(true, this.config.maxMessage)
949
+ this.ws = made
950
+ return made
951
+ }
952
+
848
953
  /** `next` once the slot has switched to WebSocket. */
849
954
  nextFrame(): i32 {
850
955
  while (true) {
851
956
  if (this.pongPending && !this.sendPong()) {
852
957
  return H1_NEED_MORE
853
958
  }
854
- const event: i32 = this.ws.next()
959
+ const ws: WsDecoder = this.decoder()
960
+ const event: i32 = ws.next()
855
961
  if (event === WS_MESSAGE) {
856
- this.opcode = this.ws.opcode
857
- this.data = this.ws.data
962
+ this.opcode = ws.opcode
963
+ this.data = ws.data
858
964
  this.dataStart = 0
859
- this.dataLength = this.ws.dataLen
965
+ this.dataLength = ws.dataLen
860
966
  return H1_WS_MESSAGE
861
967
  }
862
968
  if (event === WS_PING) {
863
- const n: i32 = this.ws.dataLen
864
- for (let k: i32 = 0; k < n && k < toI32(this.pong.length) && k < toI32(this.ws.data.length); k++) {
865
- this.pong[k] = this.ws.data[k]
969
+ const n: i32 = ws.dataLen
970
+ if (this.pong.length === 0) {
971
+ this.pong = new Array<u8>(WS_MAX_CONTROL)
972
+ }
973
+ for (let k: i32 = 0; k < n && k < toI32(this.pong.length) && k < toI32(ws.data.length); k++) {
974
+ this.pong[k] = ws.data[k]
866
975
  }
867
976
  this.pongLength = n
868
977
  this.pongPending = true
869
978
  } else if (event === WS_CLOSE) {
870
- this.status = this.ws.closeCode
871
- this.sendClose(this.ws.closeCode)
979
+ this.status = ws.closeCode
980
+ this.sendClose(ws.closeCode)
872
981
  this.state = H1_S_CLOSING
873
982
  return H1_WS_CLOSE
874
983
  } else if (event === WS_ERROR) {
875
- this.status = this.ws.closeCode
876
- this.sendClose(this.ws.closeCode)
984
+ this.status = ws.closeCode
985
+ this.sendClose(ws.closeCode)
877
986
  this.state = H1_S_FAILED
878
987
  return H1_ERROR
879
988
  } else if (event === WS_NEED_MORE) {
@@ -913,7 +1022,7 @@ export class Http1Connection {
913
1022
  const payload: u8[] | null = websocketClosePayload(code, "")
914
1023
  // A code that may not be sent (1005, for a close that had none) is
915
1024
  // answered with an empty close.
916
- const body: u8[] = payload === null ? this.pong : payload
1025
+ const body: u8[] = payload === null ? this.noOutput : payload
917
1026
  const n: i32 = payload === null ? H1_ZERO : toI32(body.length)
918
1027
  const end: i32 = websocketWriteFrame(
919
1028
  this.output,
@@ -1185,6 +1294,68 @@ export class Http1Connection {
1185
1294
  idle(now: i64): boolean {
1186
1295
  return now - this.lastActive > toI64(this.config.idleTimeout) * toI64(1000000)
1187
1296
  }
1297
+
1298
+ /**
1299
+ * Starts the head deadline at `now` (`monotonicNanos`) when bytes of a
1300
+ * request head wait to be read and its clock has not started. The carriers
1301
+ * call it after each `feed`; a program that feeds a connection itself calls
1302
+ * it the same way. Bytes that wait while the previous exchange is still
1303
+ * open start no clock: they count from when it is over (`nextRequest`).
1304
+ */
1305
+ headArrived(now: i64): void {
1306
+ if (this.parser.buffered() > 0) {
1307
+ this.headAwaited(now)
1308
+ }
1309
+ }
1310
+
1311
+ /**
1312
+ * Starts the head deadline at `now` (`monotonicNanos`) when the connection
1313
+ * waits for a head and its clock has not started, whether or not a byte of
1314
+ * it has been read. `Http1TlsServer` calls it when ciphertext arrives after
1315
+ * the handshake: a record trickled a byte at a time hands no plaintext to
1316
+ * `headArrived` until it is whole, so its bytes would otherwise only keep
1317
+ * the slot from going idle.
1318
+ */
1319
+ headAwaited(now: i64): void {
1320
+ if (!this.headTimed && this.state === H1_S_HTTP && !this.requestOpen) {
1321
+ this.headTimed = true
1322
+ this.headSince = now
1323
+ }
1324
+ }
1325
+
1326
+ /** Whether a head has been arriving for longer than `headTimeout` at `now` (`monotonicNanos`). */
1327
+ headLate(now: i64): boolean {
1328
+ return this.headTimed && now - this.headSince > toI64(this.config.headTimeout) * toI64(1000000)
1329
+ }
1330
+
1331
+ /** Whether `expire` closes the slot at `now`: idle, or a head past its deadline. */
1332
+ expired(now: i64): boolean {
1333
+ return this.idle(now) || this.headLate(now)
1334
+ }
1335
+
1336
+ /**
1337
+ * The soonest `expired` can turn true, however the connection moves from
1338
+ * here: `lastActive` only grows, and a head clock not yet started starts
1339
+ * no earlier than it.
1340
+ */
1341
+ expiresNoSooner(): i64 {
1342
+ const idle: i64 = toI64(this.config.idleTimeout) * toI64(1000000)
1343
+ const head: i64 = toI64(this.config.headTimeout) * toI64(1000000)
1344
+ const soonest: i64 = this.lastActive + (head < idle ? head : idle)
1345
+ if (this.headTimed && this.headSince + head < soonest) {
1346
+ return this.headSince + head
1347
+ }
1348
+ return soonest
1349
+ }
1350
+ }
1351
+
1352
+ /**
1353
+ * The soonest any slot accepted after now can expire: no sooner than the
1354
+ * shorter of `idleTimeout` and `headTimeout` from now.
1355
+ */
1356
+ const http1ExpireFloor = (config: Http1Config): i64 => {
1357
+ const shorter: i32 = config.headTimeout < config.idleTimeout ? config.headTimeout : config.idleTimeout
1358
+ return monotonicNanos() + toI64(shorter) * toI64(1000000)
1188
1359
  }
1189
1360
 
1190
1361
  // ---- Carriers -------------------------------------------------------------------
@@ -1213,6 +1384,9 @@ export class Http1Server {
1213
1384
  armed: i32[]
1214
1385
  /** The output buffers the slots borrow while they write. */
1215
1386
  outputs: Http1OutputPool
1387
+ /** The input and head buffers resting slots lend, with `Http1Config.idleInput`. */
1388
+ inputs: Http1OutputPool
1389
+ heads: Http1OutputPool
1216
1390
  /**
1217
1391
  * The slots closed since they were used, a stack whose top `accept` takes
1218
1392
  * before it builds a slot past the high-water mark: finding one costs the
@@ -1228,6 +1402,14 @@ export class Http1Server {
1228
1402
  slots: i32 = 1
1229
1403
  /** The listening socket, which the program made with `tcpListen` and still owns. */
1230
1404
  listener: i32 = -1
1405
+ /**
1406
+ * No slot can expire before this (`monotonicNanos`), so `expire` called
1407
+ * sooner walks none: it walks every slot only about once per the shorter
1408
+ * timeout, however many there are.
1409
+ */
1410
+ expireAfter: i64 = 0
1411
+ /** How many calls of `expire` walked every slot, for a program measuring its cost. */
1412
+ expireScans: i32 = 0
1231
1413
 
1232
1414
  /**
1233
1415
  * A pool of `size` slots, at least one, serving `config` on `listener`.
@@ -1245,6 +1427,8 @@ export class Http1Server {
1245
1427
  this.free = []
1246
1428
  this.slots = size < 1 ? 1 : size
1247
1429
  this.outputs = new Http1OutputPool(config.outputSize)
1430
+ this.inputs = new Http1OutputPool(HTTP1_FIRST_INPUT)
1431
+ this.heads = new Http1OutputPool(HTTP1_FIRST_HEAD)
1248
1432
  this.date = new HttpDate()
1249
1433
  this.scratch = new Array<u8>(config.chunkSize)
1250
1434
  this.peer = new Array<u8>(H1_ADDRESS_SIZE)
@@ -1255,6 +1439,10 @@ export class Http1Server {
1255
1439
  const conn: Http1Connection = new Http1Connection(this.config)
1256
1440
  conn.pool = this.outputs
1257
1441
  conn.date = this.date
1442
+ if (this.config.idleInput) {
1443
+ conn.inputs = this.inputs
1444
+ conn.heads = this.heads
1445
+ }
1258
1446
  this.connections.push(conn)
1259
1447
  return conn
1260
1448
  }
@@ -1369,7 +1557,9 @@ export class Http1Server {
1369
1557
  }
1370
1558
  }
1371
1559
  if (moved) {
1372
- conn.lastActive = monotonicNanos()
1560
+ const now: i64 = monotonicNanos()
1561
+ conn.lastActive = now
1562
+ conn.headArrived(now)
1373
1563
  }
1374
1564
  return this.flush(slot)
1375
1565
  }
@@ -1411,6 +1601,7 @@ export class Http1Server {
1411
1601
  if (conn.isDone()) {
1412
1602
  return H1_DONE
1413
1603
  }
1604
+ conn.park()
1414
1605
  let wants: i32 = 0
1415
1606
  if (conn.inputRoom() > 0) {
1416
1607
  wants = wants | H1_WANT_READ
@@ -1471,15 +1662,29 @@ export class Http1Server {
1471
1662
  }
1472
1663
  }
1473
1664
 
1474
- /** Closes every slot that has been idle past `idleTimeout` at `now` (`monotonicNanos`); answers how many. */
1665
+ /**
1666
+ * Closes every slot that has been idle past `idleTimeout`, or reading one
1667
+ * head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
1668
+ */
1475
1669
  expire(now: i64): i32 {
1670
+ if (now < this.expireAfter) {
1671
+ return 0
1672
+ }
1673
+ this.expireScans = this.expireScans + 1
1674
+ let soonest: i64 = http1ExpireFloor(this.config)
1476
1675
  let n: i32 = 0
1477
1676
  for (let k: i32 = 0; k < toI32(this.fds.length) && k < toI32(this.connections.length); k++) {
1478
- if (this.fds[k] >= 0 && this.connections[k].idle(now)) {
1479
- this.close(k)
1480
- n = n + 1
1677
+ if (this.fds[k] >= 0) {
1678
+ const conn = this.connections[k]
1679
+ if (conn.expired(now)) {
1680
+ this.close(k)
1681
+ n = n + 1
1682
+ } else if (conn.expiresNoSooner() < soonest) {
1683
+ soonest = conn.expiresNoSooner()
1684
+ }
1481
1685
  }
1482
1686
  }
1687
+ this.expireAfter = soonest
1483
1688
  return n
1484
1689
  }
1485
1690
  }
@@ -1515,6 +1720,13 @@ export class Http1TlsServer {
1515
1720
  scratch: u8[]
1516
1721
  /** The output buffers the slots borrow while they write. */
1517
1722
  outputs: Http1OutputPool
1723
+ /** The input and head buffers resting slots lend, with `Http1Config.idleInput`. */
1724
+ inputs: Http1OutputPool
1725
+ heads: Http1OutputPool
1726
+ /** No slot can expire before this (`monotonicNanos`): `Http1Server.expireAfter`. */
1727
+ expireAfter: i64 = 0
1728
+ /** How many calls of `expire` walked every slot. */
1729
+ expireScans: i32 = 0
1518
1730
 
1519
1731
  /**
1520
1732
  * A pool of `size` slots, at least one, serving `config` over TLS under
@@ -1528,6 +1740,8 @@ export class Http1TlsServer {
1528
1740
  this.tls = new TlsTcpServer(tlsConfig, listener, size)
1529
1741
  this.connections = []
1530
1742
  this.outputs = new Http1OutputPool(config.outputSize)
1743
+ this.inputs = new Http1OutputPool(HTTP1_FIRST_INPUT)
1744
+ this.heads = new Http1OutputPool(HTTP1_FIRST_HEAD)
1531
1745
  this.date = new HttpDate()
1532
1746
  this.scratch = new Array<u8>(config.chunkSize)
1533
1747
  }
@@ -1548,6 +1762,10 @@ export class Http1TlsServer {
1548
1762
  const conn: Http1Connection = new Http1Connection(this.config)
1549
1763
  conn.pool = this.outputs
1550
1764
  conn.date = this.date
1765
+ if (this.config.idleInput) {
1766
+ conn.inputs = this.inputs
1767
+ conn.heads = this.heads
1768
+ }
1551
1769
  return conn
1552
1770
  }
1553
1771
 
@@ -1633,10 +1851,19 @@ export class Http1TlsServer {
1633
1851
 
1634
1852
  /** The socket of `slot` is readable: TLS reads it, and what the connection has to send goes after. Answers the interest. */
1635
1853
  readable(slot: i32): i32 {
1636
- if (this.live(slot)) {
1637
- this.connections[slot].lastActive = monotonicNanos()
1854
+ const live: boolean = this.live(slot)
1855
+ const now: i64 = monotonicNanos()
1856
+ if (live) {
1857
+ this.connections[slot].lastActive = now
1638
1858
  }
1639
1859
  this.tls.readable(slot)
1860
+ // Ciphertext after the handshake is the start of a head, or of a record
1861
+ // that will hold one, so the head's clock starts here; a record trickled
1862
+ // a byte at a time would hand `next` no plaintext until it is whole. The
1863
+ // handshake itself is bounded by `idleTimeout` alone (TLS-4).
1864
+ if (live && this.tls.connection(slot).state === TLS_RECORD_STATE_OPEN) {
1865
+ this.connections[slot].headAwaited(now)
1866
+ }
1640
1867
  return this.flush(slot)
1641
1868
  }
1642
1869
 
@@ -1702,6 +1929,7 @@ export class Http1TlsServer {
1702
1929
  if (conn.isDone()) {
1703
1930
  return this.tls.shutdown(slot)
1704
1931
  }
1932
+ conn.park()
1705
1933
  const wants: i32 = this.tls.interest(slot)
1706
1934
  return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
1707
1935
  }
@@ -1737,15 +1965,29 @@ export class Http1TlsServer {
1737
1965
  }
1738
1966
  }
1739
1967
 
1740
- /** Closes every slot that has been idle past `idleTimeout` at `now` (`monotonicNanos`); answers how many. */
1968
+ /**
1969
+ * Closes every slot that has been idle past `idleTimeout`, or reading one
1970
+ * head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
1971
+ */
1741
1972
  expire(now: i64): i32 {
1973
+ if (now < this.expireAfter) {
1974
+ return 0
1975
+ }
1976
+ this.expireScans = this.expireScans + 1
1977
+ let soonest: i64 = http1ExpireFloor(this.config)
1742
1978
  let n: i32 = 0
1743
1979
  for (let k: i32 = 0; k < this.tls.size() && k < toI32(this.connections.length); k++) {
1744
- if (this.tls.holds(k) && this.connections[k].idle(now)) {
1745
- this.tls.close(k)
1746
- n = n + 1
1980
+ if (this.tls.holds(k)) {
1981
+ const conn = this.connections[k]
1982
+ if (conn.expired(now)) {
1983
+ this.tls.close(k)
1984
+ n = n + 1
1985
+ } else if (conn.expiresNoSooner() < soonest) {
1986
+ soonest = conn.expiresNoSooner()
1987
+ }
1747
1988
  }
1748
1989
  }
1990
+ this.expireAfter = soonest
1749
1991
  return n
1750
1992
  }
1751
1993
  }