@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
@@ -22,7 +22,7 @@
22
22
  * }
23
23
  * wants = server.flush(slot);
24
24
  * if ((wants & H1_DONE) !== 0) { server.close(slot); }
25
- * else { pollModify(loop, server.fd(slot), wants & 3, slot); }
25
+ * else if (server.rearm(slot, wants)) { pollModify(loop, server.fd(slot), wants & 3, slot); }
26
26
  *
27
27
  * **Events.** `next()` answers `H1_REQUEST` (a request's head: the parser's
28
28
  * spans and `...Is` methods read it), `H1_BODY` (some of its body, de-chunked,
@@ -65,6 +65,13 @@
65
65
  * §15.4.5). A 1xx before the final response (100 Continue) is allowed; 101 is
66
66
  * `acceptWebSocket`'s.
67
67
  *
68
+ * **The Date.** Every final response, an error included, carries a `Date`
69
+ * field (RFC 9110 §6.6.1) from `HttpDate`, which formats the IMF-fixdate at
70
+ * most once a second; a 1xx carries none. The pool's slots share one, so a
71
+ * busy server formats a second once, not once a slot. A program that sets its
72
+ * own `date` field sends that one instead, so a response has one `Date`.
73
+ * `date.pin(unixMs)` fixes it, for a test that compares bytes.
74
+ *
68
75
  * **Refusals.** Every refusal of `nish/net/http1`'s parser — the smuggling
69
76
  * shapes, a `Content-Length` with a `Transfer-Encoding`, two that disagree,
70
77
  * a malformed chunk size — is answered with its status (400, 413, 414, 431,
@@ -82,10 +89,21 @@
82
89
  * **Caps are start-up numbers**, all in `Http1Config`: the request target,
83
90
  * the header section and its field count, a request body, the chunk handed
84
91
  * over, the output buffer, a WebSocket message and the idle timeout; the
85
- * slots are the pool's size. Every buffer is allocated by the constructor or
86
- * grown to its cap on first use and kept, so a warmed slot serves request
87
- * after request with `Arena.used()` flat. Accepting a WebSocket allocates
88
- * (the accept key), once a connection.
92
+ * slots are the pool's size. `Http1Server` builds a slot's connection on the
93
+ * first accept into it and keeps it, so a pool sized for its peak costs at
94
+ * start-up only what does not depend on the size, and holds at most `size`
95
+ * connections once the peak has come; `Http1TlsServer` does the same for a
96
+ * slot's connection and, through `TlsTcpServer`, its TLS state. Every buffer
97
+ * is allocated with its slot or grown to its cap on first use and kept, so a
98
+ * warmed slot serves request after request with `Arena.used()` flat.
99
+ * Accepting a WebSocket allocates (the accept key), once a connection.
100
+ *
101
+ * **Output is borrowed.** The pools' slots share an `Http1OutputPool`: a
102
+ * connection takes an `outputSize` buffer when it first needs room to write
103
+ * and gives it back once its carrier has sent everything, so the output
104
+ * memory a pool holds follows the connections the socket is holding back,
105
+ * not its size. A pool that ever had `n` connections writing at once keeps
106
+ * `n` buffers and allocates no more.
89
107
  *
90
108
  * Private helpers share the importing program's flat symbol namespace
91
109
  * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
@@ -128,6 +146,7 @@ import {
128
146
  websocketWriteFrame,
129
147
  } from "nish/net/websocket"
130
148
  import { TlsServerConfig } from "nish/net/tls"
149
+ import { HTTP_DATE_LAST_SECOND, HTTP_DATE_SIZE, httpDateWrite } from "nish/net/http-fields"
131
150
  import { TLS_RECORD_DONE, TLS_RECORD_STATE_OPEN, TLS_RECORD_WANT_WRITE } from "nish/net/tls/record-server"
132
151
  import { TlsTcpServer } from "nish/net/tls-tcp"
133
152
 
@@ -228,6 +247,82 @@ const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 =
228
247
  return at + n
229
248
  }
230
249
 
250
+ /**
251
+ * The `Date` field an origin server with a clock sends on every final
252
+ * response (RFC 9110 §6.6.1): the IMF-fixdate of now, in 29 bytes the object
253
+ * owns and rewrites in place, at most once a second.
254
+ *
255
+ * It reads the wall clock, `Date.now()`, which a wasm32 build has no host for,
256
+ * so it lives here, in a module that is native already, while the formatting
257
+ * it calls, `httpDateWrite`, stays in `nish/net/http-fields`, which compiles
258
+ * for wasm32 (the split `nish/crypto/x509` and `x509-random` make). `pin`
259
+ * replaces the clock with one instant, which is how a test asserts a response
260
+ * byte for byte.
261
+ */
262
+ export class HttpDate {
263
+ /** The IMF-fixdate of `second`: what `current` answers, made once. */
264
+ bytes: u8[]
265
+ /** The second `bytes` holds, since 1970; -1 before the first `current`. */
266
+ second: i64 = -1
267
+ /** The instant `pin` fixed, in milliseconds since 1970, while `pinned`. */
268
+ pinnedMs: i64 = 0
269
+ /** How many times `bytes` has been rewritten. */
270
+ rewrites: i64 = 0
271
+ pinned: boolean = false
272
+
273
+ /** A date on the wall clock, with nothing formatted yet. */
274
+ constructor() {
275
+ this.bytes = new Array<u8>(HTTP_DATE_SIZE)
276
+ }
277
+
278
+ /**
279
+ * The 29 bytes for now, which are rewritten only when the second differs
280
+ * from the one they hold — earlier as well as later, since a corrected clock
281
+ * steps back. It allocates nothing, and the array is the same one each time,
282
+ * so a caller copies it out rather than keeping it. A wall clock before 1970
283
+ * or past 9999 answers the nearer of the two ends.
284
+ */
285
+ current(): u8[] {
286
+ const ms: i64 = this.pinned ? this.pinnedMs : toI64(Date.now())
287
+ let second: i64 = ms / 1000
288
+ if (ms < 0) {
289
+ second = 0
290
+ } else if (second > HTTP_DATE_LAST_SECOND) {
291
+ second = HTTP_DATE_LAST_SECOND
292
+ }
293
+ if (second !== this.second) {
294
+ httpDateWrite(this.bytes, 0, second)
295
+ this.second = second
296
+ this.rewrites += 1
297
+ }
298
+ return this.bytes
299
+ }
300
+
301
+ /**
302
+ * Makes `current` answer `unixMs`, milliseconds since 1970, until `unpin`.
303
+ * False, and nothing changed, for an instant before 1970-01-01T00:00:00Z or
304
+ * after 9999-12-31T23:59:59.999Z, which an IMF-fixdate cannot name.
305
+ */
306
+ pin(unixMs: i64): boolean {
307
+ if (unixMs < 0 || unixMs / 1000 > HTTP_DATE_LAST_SECOND) {
308
+ return false
309
+ }
310
+ this.pinnedMs = unixMs
311
+ this.pinned = true
312
+ return true
313
+ }
314
+
315
+ /** Puts `current` back on the wall clock. */
316
+ unpin(): void {
317
+ this.pinned = false
318
+ }
319
+
320
+ /** How many times the bytes have been rewritten: one per second that `current` was asked for, at most. */
321
+ formats(): i64 {
322
+ return this.rewrites
323
+ }
324
+ }
325
+
231
326
  /**
232
327
  * The caps of every connection made from it, fixed when the connection is
233
328
  * made. Each is a number the program sets once, at start-up.
@@ -311,6 +406,44 @@ const http1ServerReserved = (name: u8[]): boolean =>
311
406
  http1BytesAre(name, "transfer-encoding") ||
312
407
  http1BytesAre(name, "content-length")
313
408
 
409
+ /**
410
+ * Output buffers shared by the connections of one pool, each `size` bytes. A
411
+ * connection borrows one when it has something to write and gives it back
412
+ * once its carrier has sent everything, so a pool's output memory follows the
413
+ * connections the socket is holding back, not the slots: Go's `net/http`
414
+ * pools its writers the same way, and uWebSockets shares one per loop. A
415
+ * buffer is made only when none is free, so the pool grows to the most
416
+ * connections ever writing at once and then allocates nothing.
417
+ */
418
+ export class Http1OutputPool {
419
+ // Widest first, so the layout packs without padding.
420
+ free: u8[][]
421
+ size: i32 = 0
422
+ /** How many buffers the pool has made. */
423
+ made: i32 = 0
424
+
425
+ constructor(size: i32) {
426
+ this.size = size
427
+ this.free = []
428
+ }
429
+
430
+ /** A buffer of `size` bytes: the one given back last, or a new one. */
431
+ take(): u8[] {
432
+ if (this.free.length > 0) {
433
+ const buf: u8[] = this.free[this.free.length - 1]
434
+ this.free.pop()
435
+ return buf
436
+ }
437
+ this.made = this.made + 1
438
+ return new Array<u8>(this.size)
439
+ }
440
+
441
+ /** Takes back a buffer `take` gave. */
442
+ give(buf: u8[]): void {
443
+ this.free.push(buf)
444
+ }
445
+ }
446
+
314
447
  /**
315
448
  * One connection's HTTP/1.1 server, sans-IO: bytes in through `feed`, events
316
449
  * out of `next`, and the response the program writes in
@@ -320,12 +453,27 @@ export class Http1Connection {
320
453
  // References, then 64-bit fields, then 32-bit ones, then flags: the order
321
454
  // the layout packs without padding.
322
455
  config: Http1Config
456
+ /**
457
+ * The `Date` every final response carries. The connection makes its own;
458
+ * `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
459
+ * pool shares one and the pool formats at most once a second. A program may
460
+ * put another, to share it or to pin its clock.
461
+ */
462
+ date: HttpDate
323
463
  /** The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is the last `H1_REQUEST`'s. */
324
464
  parser: Http1Parser
325
465
  /** The frame decoder, once the slot has switched to WebSocket. */
326
466
  ws: WsDecoder
327
- /** Bytes to send are `output[outputStart .. outputEnd)`. */
467
+ /**
468
+ * Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
469
+ * connection first needs room to write: then its own buffer, made once, or
470
+ * one borrowed from `pool` and given back by `release` once sent.
471
+ */
328
472
  output: u8[]
473
+ /** The empty array `output` is while the connection holds no buffer. */
474
+ noOutput: u8[]
475
+ /** Where `output` is borrowed from, for a connection of a pool; null for one that keeps its own. */
476
+ pool: Http1OutputPool | null
329
477
  /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
330
478
  data: u8[]
331
479
  /** A ping's payload, kept until its pong fits. */
@@ -396,12 +544,15 @@ export class Http1Connection {
396
544
  big - H1_LINE_OVERHEAD
397
545
  )
398
546
  this.config = config
547
+ this.date = new HttpDate()
399
548
  this.parser = new Http1Parser(config.maxTarget, config.maxHeaderBytes, config.maxHeaders, config.maxBody)
400
549
  this.parser.keepText = false
401
550
  this.parser.maxChunk = config.chunkSize
402
551
  this.ws = new WsDecoder(true, config.maxMessage)
403
- this.output = new Array<u8>(config.outputSize)
404
- this.data = this.output
552
+ this.noOutput = []
553
+ this.output = this.noOutput
554
+ this.pool = null
555
+ this.data = this.noOutput
405
556
  this.pong = new Array<u8>(WS_MAX_CONTROL)
406
557
  this.noFields = []
407
558
  // A line must fit whole before the parser can read it, so the buffer
@@ -423,7 +574,7 @@ export class Http1Connection {
423
574
  this.ws.reset()
424
575
  this.outputStart = 0
425
576
  this.outputEnd = 0
426
- this.data = this.output
577
+ this.data = this.noOutput
427
578
  this.dataStart = 0
428
579
  this.dataLength = 0
429
580
  this.opcode = 0
@@ -441,6 +592,7 @@ export class Http1Connection {
441
592
  this.writeHeld = false
442
593
  this.pongPending = false
443
594
  this.inputEnded = false
595
+ this.release()
444
596
  }
445
597
 
446
598
  // ---- Bytes in and out ---------------------------------------------------------
@@ -492,6 +644,44 @@ export class Http1Connection {
492
644
  this.outputEnd = 0
493
645
  this.pongPending = false
494
646
  this.writeHeld = false
647
+ this.release()
648
+ }
649
+
650
+ /**
651
+ * Borrows `output` from the pool between connections of one, for a
652
+ * connection that keeps its own makes it the first time: before anything
653
+ * is written into it, which is why `room` calls it. It allocates only when
654
+ * the pool has no free buffer, or for a connection's own, once.
655
+ */
656
+ acquire(): void {
657
+ if (toI32(this.output.length) > 0) {
658
+ return
659
+ }
660
+ const pool: Http1OutputPool | null = this.pool
661
+ this.output = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
662
+ this.outputStart = 0
663
+ this.outputEnd = 0
664
+ }
665
+
666
+ /**
667
+ * Gives `output` back to the pool once nothing in it is waiting: no bytes
668
+ * to send, no write held back for room. The carrier calls it after each
669
+ * send; a connection that keeps its own buffer keeps it.
670
+ */
671
+ release(): void {
672
+ const pool: Http1OutputPool | null = this.pool
673
+ if (
674
+ pool === null ||
675
+ toI32(this.output.length) === 0 ||
676
+ this.outputEnd > this.outputStart ||
677
+ this.writeHeld
678
+ ) {
679
+ return
680
+ }
681
+ pool.give(this.output)
682
+ this.output = this.noOutput
683
+ this.outputStart = 0
684
+ this.outputEnd = 0
495
685
  }
496
686
 
497
687
  /** Marks the first `n` bytes of the output sent. */
@@ -518,6 +708,7 @@ export class Http1Connection {
518
708
 
519
709
  /** The room at the end of the output, once what was sent is moved out of the way. */
520
710
  room(): i32 {
711
+ this.acquire()
521
712
  const output: u8[] = this.output
522
713
  const live: i32 = this.outputEnd - this.outputStart
523
714
  if (this.outputStart > 0) {
@@ -550,7 +741,7 @@ export class Http1Connection {
550
741
  if (this.writeHeld) {
551
742
  // Half the output free before the program is asked to write again, so
552
743
  // that a slow reader is not answered one small write at a time.
553
- if (this.bodyRoom() * 2 < toI32(this.output.length) - H1_CONTROL_RESERVE) {
744
+ if (this.bodyRoom() * 2 < this.config.outputSize - H1_CONTROL_RESERVE) {
554
745
  return H1_NEED_MORE
555
746
  }
556
747
  this.writeHeld = false
@@ -642,7 +833,8 @@ export class Http1Connection {
642
833
  this.noFields,
643
834
  this.noFields,
644
835
  H1_ZERO,
645
- "close"
836
+ "close",
837
+ this.date.current()
646
838
  )
647
839
  if (end >= 0) {
648
840
  this.outputEnd = end
@@ -794,14 +986,15 @@ export class Http1Connection {
794
986
  names,
795
987
  values,
796
988
  framing,
797
- connection
989
+ connection,
990
+ this.date.current()
798
991
  )
799
992
  if (end === HTTP1_REFUSED) {
800
993
  return H1_INVALID
801
994
  }
802
995
  if (end < 0 || end - this.outputEnd > room) {
803
996
  // The head was not written, or ate into the reserve: take it back.
804
- if (end - this.outputEnd > toI32(this.output.length) - H1_CONTROL_RESERVE) {
997
+ if (end - this.outputEnd > this.config.outputSize - H1_CONTROL_RESERVE) {
805
998
  return H1_INVALID
806
999
  }
807
1000
  if (this.outputEnd === 0) {
@@ -950,7 +1143,7 @@ export class Http1Connection {
950
1143
  if (this.state !== H1_S_WEBSOCKET) {
951
1144
  return H1_CLOSED
952
1145
  }
953
- if (opcode === WS_OP_CLOSE || len > toI32(this.output.length) - H1_CONTROL_RESERVE - 14) {
1146
+ if (opcode === WS_OP_CLOSE || len > this.config.outputSize - H1_CONTROL_RESERVE - 14) {
954
1147
  return H1_INVALID
955
1148
  }
956
1149
  if (websocketFrameSize(len, false) > this.bodyRoom()) {
@@ -1003,34 +1196,72 @@ export class Http1Connection {
1003
1196
  */
1004
1197
  export class Http1Server {
1005
1198
  config: Http1Config
1199
+ /**
1200
+ * The connections built so far, slot `k`'s at `k`: one per slot ever
1201
+ * accepted into, and slot 0's once `connection` has asked for it.
1202
+ */
1006
1203
  connections: Http1Connection[]
1007
- /** Each slot's descriptor, or -1 while it is free. */
1204
+ /** The `Date` every slot's responses carry, formatted once a second for all of them. `date.pin` fixes its clock. */
1205
+ date: HttpDate
1206
+ /**
1207
+ * Each slot's descriptor, or -1 while it is free, for the slots accepted
1208
+ * into so far: its length is the high-water mark, and a slot past it has
1209
+ * never been used.
1210
+ */
1008
1211
  fds: i32[]
1212
+ /** Each used slot's interest as the loop last armed it, for `rearm`. */
1213
+ armed: i32[]
1214
+ /** The output buffers the slots borrow while they write. */
1215
+ outputs: Http1OutputPool
1216
+ /**
1217
+ * The slots closed since they were used, a stack whose top `accept` takes
1218
+ * before it builds a slot past the high-water mark: finding one costs the
1219
+ * same with every slot free as with all but one busy, where a scan of
1220
+ * `fds` walked the busy ones.
1221
+ */
1222
+ free: i32[]
1009
1223
  /** Where the socket is read into before the connection takes the bytes. */
1010
1224
  scratch: u8[]
1011
1225
  /** The last accepted peer's address, in `nish:net`'s 18-byte form. */
1012
1226
  peer: u8[]
1227
+ /** How many slots the pool may hold, which `size` answers. */
1228
+ slots: i32 = 1
1013
1229
  /** The listening socket, which the program made with `tcpListen` and still owns. */
1014
1230
  listener: i32 = -1
1015
1231
 
1016
- /** A pool of `size` slots, at least one, serving `config` on `listener`. Every slot's buffers are allocated here. */
1232
+ /**
1233
+ * A pool of `size` slots, at least one, serving `config` on `listener`.
1234
+ * No slot is built here: a slot's connection and its buffers are
1235
+ * allocated by the first `accept` into it and kept, so a pool sized for a
1236
+ * peak costs at start-up only the read scratch, the shared `Date` and the
1237
+ * output pool, and at most `size` connections once the peak has come.
1238
+ */
1017
1239
  constructor(config: Http1Config, listener: i32, size: i32) {
1018
1240
  this.config = config
1019
1241
  this.listener = listener
1020
1242
  this.connections = []
1021
1243
  this.fds = []
1022
- const slots: i32 = size < 1 ? 1 : size
1023
- for (let k: i32 = 0; k < slots; k++) {
1024
- this.connections.push(new Http1Connection(config))
1025
- this.fds.push(H1_FREE)
1026
- }
1244
+ this.armed = []
1245
+ this.free = []
1246
+ this.slots = size < 1 ? 1 : size
1247
+ this.outputs = new Http1OutputPool(config.outputSize)
1248
+ this.date = new HttpDate()
1027
1249
  this.scratch = new Array<u8>(config.chunkSize)
1028
1250
  this.peer = new Array<u8>(H1_ADDRESS_SIZE)
1029
1251
  }
1030
1252
 
1031
- /** How many slots the pool has. */
1253
+ /** A new connection on this pool's outputs and `Date`, for the next slot built. */
1254
+ build(): Http1Connection {
1255
+ const conn: Http1Connection = new Http1Connection(this.config)
1256
+ conn.pool = this.outputs
1257
+ conn.date = this.date
1258
+ this.connections.push(conn)
1259
+ return conn
1260
+ }
1261
+
1262
+ /** How many slots the pool has, built or not. */
1032
1263
  size(): i32 {
1033
- return toI32(this.fds.length)
1264
+ return this.slots
1034
1265
  }
1035
1266
 
1036
1267
  /** How many slots hold a connection. */
@@ -1054,36 +1285,57 @@ export class Http1Server {
1054
1285
  return this.holds(slot) ? this.fds[slot] : -1
1055
1286
  }
1056
1287
 
1057
- /** The connection of `slot`; a slot out of range names the first. */
1288
+ /**
1289
+ * The connection of `slot`. A slot never accepted into, or out of range,
1290
+ * names slot 0's connection, which this builds when no slot has been yet
1291
+ * (the first `accept` then takes it, restarted).
1292
+ */
1058
1293
  connection(slot: i32): Http1Connection {
1059
- const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : H1_ZERO
1060
- return this.connections[at]
1294
+ if (slot >= 0 && slot < toI32(this.connections.length)) {
1295
+ return this.connections[slot]
1296
+ }
1297
+ return this.connections.length > 0 ? this.connections[0] : this.build()
1061
1298
  }
1062
1299
 
1063
1300
  /**
1064
1301
  * Accepts the next waiting connection into a free slot and answers the
1065
- * slot; -11 when none is waiting, `H1_POOL_FULL` when every slot is busy
1066
- * (the connection is closed), or `tcpAccept`'s own failure.
1302
+ * slot, which the program adds to its loop with `H1_WANT_READ`; -11 when
1303
+ * none is waiting, `H1_POOL_FULL` when every slot is busy (the connection
1304
+ * is closed), or `tcpAccept`'s own failure.
1067
1305
  */
1068
1306
  accept(): i32 {
1069
1307
  const fd: i32 = tcpAccept(this.listener, this.peer)
1070
1308
  if (fd < 0) {
1071
1309
  return fd
1072
1310
  }
1073
- for (let k: i32 = 0; k < toI32(this.fds.length) && k < toI32(this.connections.length); k++) {
1074
- if (this.fds[k] < 0) {
1075
- this.fds[k] = fd
1076
- const conn: Http1Connection = this.connections[k]
1077
- conn.restart()
1078
- conn.lastActive = monotonicNanos()
1079
- return k
1080
- }
1081
- }
1082
- netClose(fd)
1083
- return H1_POOL_FULL
1311
+ // A slot closed before is taken first, then the next never used, so the
1312
+ // first accepts take slots 0, 1, 2 ... and a pool that never fills never
1313
+ // builds its last slots.
1314
+ let k: i32 = H1_POOL_FULL
1315
+ if (this.free.length > 0) {
1316
+ k = this.free[this.free.length - 1]
1317
+ this.free.pop()
1318
+ } else if (toI32(this.fds.length) < this.slots) {
1319
+ k = toI32(this.fds.length)
1320
+ this.fds.push(H1_FREE)
1321
+ this.armed.push(H1_WANT_READ)
1322
+ }
1323
+ if (k < 0 || k >= toI32(this.fds.length) || k >= toI32(this.armed.length)) {
1324
+ netClose(fd)
1325
+ return H1_POOL_FULL
1326
+ }
1327
+ const conn: Http1Connection = k < toI32(this.connections.length) ? this.connections[k] : this.build()
1328
+ this.fds[k] = fd
1329
+ this.armed[k] = H1_WANT_READ
1330
+ conn.restart()
1331
+ conn.lastActive = monotonicNanos()
1332
+ return k
1084
1333
  }
1085
1334
 
1086
- /** The socket of `slot` is readable: reads it while the connection has room, then sends. Answers the interest. */
1335
+ /**
1336
+ * The socket of `slot` is readable: reads it while the connection has room
1337
+ * and the socket has more, then sends. Answers the interest.
1338
+ */
1087
1339
  readable(slot: i32): i32 {
1088
1340
  if (!this.holds(slot)) {
1089
1341
  return H1_DONE
@@ -1108,6 +1360,13 @@ export class Http1Server {
1108
1360
  }
1109
1361
  moved = true
1110
1362
  conn.feed(this.scratch, H1_ZERO, n)
1363
+ // A short read emptied the socket. The loop's readiness is
1364
+ // level-triggered, so more input, or the peer's close, wakes it again;
1365
+ // reading once more here would only answer -11, at the price of a
1366
+ // system call on every request.
1367
+ if (n < want) {
1368
+ break
1369
+ }
1111
1370
  }
1112
1371
  if (moved) {
1113
1372
  conn.lastActive = monotonicNanos()
@@ -1180,6 +1439,23 @@ export class Http1Server {
1180
1439
  if (conn.outputEnd - conn.outputStart < pending) {
1181
1440
  conn.lastActive = monotonicNanos()
1182
1441
  }
1442
+ conn.release()
1443
+ }
1444
+
1445
+ /**
1446
+ * Whether the program must `pollModify` `slot`'s descriptor to `wants`, the
1447
+ * interest `readable`, `writable` or `flush` answered, and records it as
1448
+ * armed when it must. A keep-alive connection answers the same interest
1449
+ * request after request, and each `pollModify` is a system call
1450
+ * (`epoll_ctl`) that changes nothing.
1451
+ */
1452
+ rearm(slot: i32, wants: i32): boolean {
1453
+ const interest: i32 = wants & (H1_WANT_READ | H1_WANT_WRITE)
1454
+ if (!this.holds(slot) || slot >= toI32(this.armed.length) || this.armed[slot] === interest) {
1455
+ return false
1456
+ }
1457
+ this.armed[slot] = interest
1458
+ return true
1183
1459
  }
1184
1460
 
1185
1461
  /** Closes `slot`'s socket, whatever is left unsent, and frees the slot. */
@@ -1189,6 +1465,10 @@ export class Http1Server {
1189
1465
  }
1190
1466
  netClose(this.fds[slot])
1191
1467
  this.fds[slot] = H1_FREE
1468
+ this.free.push(slot)
1469
+ if (slot < toI32(this.connections.length)) {
1470
+ this.connections[slot].abort()
1471
+ }
1192
1472
  }
1193
1473
 
1194
1474
  /** Closes every slot that has been idle past `idleTimeout` at `now` (`monotonicNanos`); answers how many. */
@@ -1209,25 +1489,69 @@ export class Http1Server {
1209
1489
  * `Http1Connection` beside each slot of a `TlsTcpServer`, run the way
1210
1490
  * `nish/net/http2-tls` runs HTTP/2. A handshake that chose ALPN `http/1.1`,
1211
1491
  * or none (a client that does not send ALPN speaks HTTP/1.1), reaches its
1212
- * connection; any other protocol is shut down with `close_notify`.
1492
+ * connection; any other protocol is shut down with `close_notify`. A slot's
1493
+ * connection is built on the first accept into it and kept, as
1494
+ * `Http1Server`'s is.
1213
1495
  */
1214
1496
  export class Http1TlsServer {
1497
+ config: Http1Config
1215
1498
  tls: TlsTcpServer
1499
+ /**
1500
+ * The connections built so far, slot `k`'s at `k`: one per slot ever
1501
+ * accepted into. `connection` never adds to it; a slot never built reads
1502
+ * `vacant` instead.
1503
+ */
1216
1504
  connections: Http1Connection[]
1505
+ /**
1506
+ * What `connection` answers for every slot never built: one connection no
1507
+ * slot holds and every such slot shares, so the slot never reads as
1508
+ * another slot's live request. Its first read allocates it, once per
1509
+ * server, and a caller must not change it.
1510
+ */
1511
+ vacant: Http1Connection | null = null
1512
+ /** The `Date` every slot's responses carry, as `Http1Server.date` is. */
1513
+ date: HttpDate
1217
1514
  /** Where TLS decrypts into before the connection takes the bytes. */
1218
1515
  scratch: u8[]
1516
+ /** The output buffers the slots borrow while they write. */
1517
+ outputs: Http1OutputPool
1219
1518
 
1220
- /** A pool of `size` slots, at least one, serving `config` over TLS under `tlsConfig` on `listener`. */
1519
+ /**
1520
+ * A pool of `size` slots, at least one, serving `config` over TLS under
1521
+ * `tlsConfig` on `listener`. No slot is built here: the first `accept` into
1522
+ * a slot builds its connection and, in `tls`, its handshake state, and
1523
+ * keeps them, so a pool sized for a peak costs at start-up only the read
1524
+ * scratches, the shared `Date` and the empty pools.
1525
+ */
1221
1526
  constructor(tlsConfig: TlsServerConfig, config: Http1Config, listener: i32, size: i32) {
1527
+ this.config = config
1222
1528
  this.tls = new TlsTcpServer(tlsConfig, listener, size)
1223
1529
  this.connections = []
1224
- for (let k: i32 = 0; k < this.tls.size(); k++) {
1225
- this.connections.push(new Http1Connection(config))
1226
- }
1530
+ this.outputs = new Http1OutputPool(config.outputSize)
1531
+ this.date = new HttpDate()
1227
1532
  this.scratch = new Array<u8>(config.chunkSize)
1228
1533
  }
1229
1534
 
1230
- /** How many slots the pool has. */
1535
+ /**
1536
+ * A new connection on this pool's outputs and `Date`, for the next slot
1537
+ * built. Called by `accept` alone, as `tls` builds slots: slot `k`'s
1538
+ * connection must sit at index `k` of `connections`.
1539
+ */
1540
+ build(): Http1Connection {
1541
+ const conn: Http1Connection = this.make()
1542
+ this.connections.push(conn)
1543
+ return conn
1544
+ }
1545
+
1546
+ /** A connection on this pool's outputs and `Date` that no slot holds yet. */
1547
+ make(): Http1Connection {
1548
+ const conn: Http1Connection = new Http1Connection(this.config)
1549
+ conn.pool = this.outputs
1550
+ conn.date = this.date
1551
+ return conn
1552
+ }
1553
+
1554
+ /** How many slots the pool has, built or not. */
1231
1555
  size(): i32 {
1232
1556
  return this.tls.size()
1233
1557
  }
@@ -1247,10 +1571,32 @@ export class Http1TlsServer {
1247
1571
  return this.tls.fd(slot)
1248
1572
  }
1249
1573
 
1250
- /** The HTTP/1.1 connection of `slot`; a slot out of range names the first. */
1574
+ /**
1575
+ * Whether `slot` holds a connection and has its HTTP/1.1 connection built,
1576
+ * as `Http2TlsServer.live` asks: a slot accepted on `tls` directly holds a
1577
+ * TLS session and no `Http1Connection`, and reads as free here.
1578
+ */
1579
+ live(slot: i32): boolean {
1580
+ return this.tls.holds(slot) && slot < toI32(this.connections.length)
1581
+ }
1582
+
1583
+ /**
1584
+ * The HTTP/1.1 connection of `slot`. A slot never accepted into answers
1585
+ * `vacant`, as a free slot answers a connection that holds nothing, and
1586
+ * builds no slot; a slot out of range names slot 0's.
1587
+ */
1251
1588
  connection(slot: i32): Http1Connection {
1252
- const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : H1_ZERO
1253
- return this.connections[at]
1589
+ const index: i32 = slot >= 0 && slot < this.tls.size() ? slot : 0
1590
+ if (index >= 0 && index < toI32(this.connections.length)) {
1591
+ return this.connections[index]
1592
+ }
1593
+ const vacant: Http1Connection | null = this.vacant
1594
+ if (vacant !== null) {
1595
+ return vacant
1596
+ }
1597
+ const made: Http1Connection = this.make()
1598
+ this.vacant = made
1599
+ return made
1254
1600
  }
1255
1601
 
1256
1602
  /** The ALPN protocol `slot`'s handshake chose, or empty. */
@@ -1267,11 +1613,17 @@ export class Http1TlsServer {
1267
1613
  /**
1268
1614
  * Accepts the next waiting connection as `TlsTcpServer.accept` does, with
1269
1615
  * the caller's 32-byte server random and x25519 key, and gives it a fresh
1270
- * connection. Answers the slot, or what `TlsTcpServer.accept` answers.
1616
+ * connection, building the slot's on its first accept. Answers the slot, or
1617
+ * what `TlsTcpServer.accept` answers.
1271
1618
  */
1272
1619
  accept(serverRandom: u8[], ephemeralPrivate: u8[]): i32 {
1273
1620
  const slot: i32 = this.tls.accept(serverRandom, ephemeralPrivate)
1274
- if (slot >= 0 && slot < toI32(this.connections.length)) {
1621
+ if (slot >= 0) {
1622
+ // `tls` builds a slot past its high-water mark one at a time, so this
1623
+ // builds at most the one connection, keeping slot `k`'s at `k`.
1624
+ while (toI32(this.connections.length) <= slot) {
1625
+ this.build()
1626
+ }
1275
1627
  const conn: Http1Connection = this.connections[slot]
1276
1628
  conn.restart()
1277
1629
  conn.lastActive = monotonicNanos()
@@ -1281,7 +1633,7 @@ export class Http1TlsServer {
1281
1633
 
1282
1634
  /** The socket of `slot` is readable: TLS reads it, and what the connection has to send goes after. Answers the interest. */
1283
1635
  readable(slot: i32): i32 {
1284
- if (this.holds(slot)) {
1636
+ if (this.live(slot)) {
1285
1637
  this.connections[slot].lastActive = monotonicNanos()
1286
1638
  }
1287
1639
  this.tls.readable(slot)
@@ -1306,7 +1658,7 @@ export class Http1TlsServer {
1306
1658
  * slot, or one whose ALPN was another protocol, answers `H1_ERROR`.
1307
1659
  */
1308
1660
  next(slot: i32): i32 {
1309
- if (!this.holds(slot) || this.refused(slot)) {
1661
+ if (!this.live(slot) || this.refused(slot)) {
1310
1662
  return H1_ERROR
1311
1663
  }
1312
1664
  const conn: Http1Connection = this.connections[slot]
@@ -1339,7 +1691,7 @@ export class Http1TlsServer {
1339
1691
  * connection still holds bytes.
1340
1692
  */
1341
1693
  flush(slot: i32): i32 {
1342
- if (!this.holds(slot)) {
1694
+ if (!this.live(slot)) {
1343
1695
  return TLS_RECORD_DONE
1344
1696
  }
1345
1697
  if (this.refused(slot)) {
@@ -1354,8 +1706,14 @@ export class Http1TlsServer {
1354
1706
  return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
1355
1707
  }
1356
1708
 
1357
- /** Writes what `slot`'s connection holds into TLS, as far as TLS takes it. */
1709
+ /**
1710
+ * Writes what `slot`'s connection holds into TLS, as far as TLS takes it;
1711
+ * a slot never built has nothing to write.
1712
+ */
1358
1713
  send(slot: i32): void {
1714
+ if (slot < 0 || slot >= toI32(this.connections.length)) {
1715
+ return
1716
+ }
1359
1717
  const conn: Http1Connection = this.connections[slot]
1360
1718
  const pending: i32 = conn.outputEnd - conn.outputStart
1361
1719
  while (conn.wantsWrite()) {
@@ -1368,11 +1726,15 @@ export class Http1TlsServer {
1368
1726
  if (conn.outputEnd - conn.outputStart < pending) {
1369
1727
  conn.lastActive = monotonicNanos()
1370
1728
  }
1729
+ conn.release()
1371
1730
  }
1372
1731
 
1373
1732
  /** Ends `slot`'s connection and frees the slot at once, as `TlsTcpServer.close` does. */
1374
1733
  close(slot: i32): void {
1375
1734
  this.tls.close(slot)
1735
+ if (slot >= 0 && slot < toI32(this.connections.length)) {
1736
+ this.connections[slot].abort()
1737
+ }
1376
1738
  }
1377
1739
 
1378
1740
  /** Closes every slot that has been idle past `idleTimeout` at `now` (`monotonicNanos`); answers how many. */