@amritk/nish-x86_64-linux 0.17.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 (61) 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 +231 -20
  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 +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  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 +1376 -42
  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 +1412 -70
  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 +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
package/std/net/http1.ts CHANGED
@@ -67,6 +67,9 @@
67
67
  * header section with the framing field the body needs, and `http1Chunk` and
68
68
  * `http1LastChunk` for a chunked body. It refuses (`null`) anything that would
69
69
  * let a caller's string end a line early — the response-splitting shape.
70
+ * `http1WriteResponseHead` writes the same head into a caller's buffer with
71
+ * nothing allocated, and adds a `Date` field from the bytes it is given
72
+ * (`httpDateWrite` in `nish/net/http-fields` makes them) to a final response.
70
73
  *
71
74
  * Field names are kept in lowercase, the form RFC 9110 §5.1 says they compare
72
75
  * in and the form HTTP/2 sends them in. Strings in the language are bytes, so a
@@ -1410,11 +1413,16 @@ const http1PutDecimal = (out: u8[], at: i32, n: i32): i32 => {
1410
1413
  * `http1ResponseHead`'s status line and header section, written into `out`
1411
1414
  * at `at` with nothing allocated: field names and values are octets (as
1412
1415
  * `nish/net/http-fields` holds them), and `connection`, when not empty, is
1413
- * sent as a `Connection` field after the caller's. Answers where the head
1414
- * ends; `HTTP1_REFUSED` for everything `http1ResponseHead` refuses, a
1415
- * `Connection` among the caller's fields when `connection` is given, and a
1416
- * `connection` that is not field text; `HTTP1_NO_ROOM` when it is well formed
1417
- * but does not fit. Either way `out` is untouched.
1416
+ * sent as a `Connection` field after the caller's. `date`, when not empty, is
1417
+ * sent as a `Date` field between the caller's fields and the framing ones
1418
+ * (RFC 9110 §6.6.1), on a final status only, and not when the caller's fields
1419
+ * already hold a `date` in any case: the caller's wins, so a response carries
1420
+ * one. Answers where the head ends; `HTTP1_REFUSED` for everything
1421
+ * `http1ResponseHead` refuses, a `Connection` among the caller's fields when
1422
+ * `connection` is given, a `connection` that is not field text, and a `date`
1423
+ * that is not field text on a final status, the only one it is sent on;
1424
+ * `HTTP1_NO_ROOM` when it is well formed but does not fit, the `Date` line
1425
+ * counted when it is sent. Either way `out` is untouched.
1418
1426
  */
1419
1427
  export const http1WriteResponseHead = (
1420
1428
  out: u8[],
@@ -1424,11 +1432,15 @@ export const http1WriteResponseHead = (
1424
1432
  names: u8[][],
1425
1433
  values: u8[][],
1426
1434
  bodyLength: i32,
1427
- connection: string
1435
+ connection: string,
1436
+ date: u8[]
1428
1437
  ): i32 => {
1429
1438
  if (status < 100 || status > 599 || !http1IsFieldText(reason) || !http1IsFieldText(connection)) {
1430
1439
  return HTTP1_REFUSED
1431
1440
  }
1441
+ if (status >= 200 && !http1BytesAreFieldText(date)) {
1442
+ return HTTP1_REFUSED
1443
+ }
1432
1444
  if (bodyLength < HTTP1_NO_BODY || ((status < 200 || status === 204) && bodyLength !== HTTP1_NO_BODY)) {
1433
1445
  return HTTP1_REFUSED
1434
1446
  }
@@ -1440,6 +1452,7 @@ export const http1WriteResponseHead = (
1440
1452
  // field, the connection field and the empty line: counted before anything
1441
1453
  // is written, in 64 bits so that no caller's lengths can wrap the sum.
1442
1454
  let need: i64 = toI64(17) + toI64(reason.length)
1455
+ let sendDate: boolean = status >= 200 && toI32(date.length) > 0
1443
1456
  for (let i: i32 = 0; i < count && i < toI32(values.length); i += 1) {
1444
1457
  const name: u8[] = names[i]
1445
1458
  const value: u8[] = values[i]
@@ -1452,8 +1465,15 @@ export const http1WriteResponseHead = (
1452
1465
  if (toI32(connection.length) > 0 && http1BytesAre(name, "connection")) {
1453
1466
  return HTTP1_REFUSED
1454
1467
  }
1468
+ if (http1BytesAre(name, "date")) {
1469
+ sendDate = false
1470
+ }
1455
1471
  need = need + toI64(name.length) + toI64(value.length) + toI64(4)
1456
1472
  }
1473
+ if (sendDate) {
1474
+ // "Date: " value CRLF: 37 bytes for an IMF-fixdate.
1475
+ need = need + toI64(8) + toI64(date.length)
1476
+ }
1457
1477
  if (bodyLength >= 0) {
1458
1478
  need = need + toI64(18 + http1DecimalDigits(bodyLength))
1459
1479
  } else if (bodyLength === HTTP1_CHUNKED) {
@@ -1476,6 +1496,11 @@ export const http1WriteResponseHead = (
1476
1496
  end = http1PutBytes(out, end, values[i])
1477
1497
  end = http1PutText(out, end, "\r\n")
1478
1498
  }
1499
+ if (sendDate) {
1500
+ end = http1PutText(out, end, "Date: ")
1501
+ end = http1PutBytes(out, end, date)
1502
+ end = http1PutText(out, end, "\r\n")
1503
+ }
1479
1504
  if (bodyLength >= 0) {
1480
1505
  end = http1PutText(out, end, "Content-Length: ")
1481
1506
  end = http1PutDecimal(out, end, bodyLength)
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * `nish/net/http2-tls` — HTTP/2 over TLS 1.3 on TCP: a pool of
3
3
  * `Http2Connection`s, one beside each slot of a `TlsTcpServer`, for a
4
- * program that owns its readiness loop.
4
+ * program that owns its readiness loop. Slot 0's connection is built with the
5
+ * server, so a bad `Http2Config` is refused there; every other slot's is built
6
+ * on the first accept into it.
5
7
  *
6
8
  * const listener = tcpListen("0.0.0.0", 8443, 128);
7
9
  * const server = new Http2TlsServer(tlsConfig, new Http2Config(), listener, 64);
@@ -36,16 +38,35 @@
36
38
  * configuration offers `h2` (and may offer `http/1.1` beside it, for a
37
39
  * program that serves both and calls `alpn(slot)` itself).
38
40
  *
39
- * **Slots, sized at start-up.** The constructor makes every connection and
40
- * the TLS pool beside them; `accept` resets the slot's connection, and
41
- * nothing a connection does after that allocates but a header block's HPACK
42
- * decoding (`docs/security/http2.md`, H2-1). The caps are the
43
- * `Http2Config`'s, one for every slot, so a program sets them once.
41
+ * **Slots, a cap; buffers, borrowed.** The constructor makes the TLS pool
42
+ * and slot 0's connection, but no other slot's and no frame buffer: a slot's
43
+ * connection is made by the first `accept` into it and kept, so a pool sized
44
+ * for its peak costs at start-up only what does not depend on the size, and
45
+ * holds at most `size` connections once the peak has come. A connection
46
+ * borrows its input, output and header-block buffers from the
47
+ * server's `Http2Pools` when it first needs each, and gives each back once it
48
+ * is empty and when the slot closes, so a free or idle slot holds none. A
49
+ * slot whose handshake is not done holds one output buffer, its SETTINGS
50
+ * written and waiting on TLS, until the handshake ends or the slot closes:
51
+ * one client that stalls its handshake keeps one buffer, as it kept a whole
52
+ * slot before (`docs/security/tls.md`, TLS-4). Each pool's ceiling is the
53
+ * slot count, so a borrow never fails. `accept` resets
54
+ * the slot's connection, and once the pools are warm nothing a connection
55
+ * does allocates but a header block's HPACK decoding (`docs/security/http2.md`,
56
+ * H2-1). The caps are the `Http2Config`'s, one for every slot, so a program
57
+ * sets them once.
58
+ *
59
+ * **The Date.** Every final response carries one `date` field (RFC 9110
60
+ * §6.6.1) from the pool's `HttpDate`, which formats the IMF-fixdate at most
61
+ * once a second for every slot; a 1xx and trailers carry none, and a program
62
+ * that sends its own `date` sends that one instead. `server.date.pin(unixMs)`
63
+ * fixes it, for a test that compares bytes.
44
64
  *
45
65
  * Written from RFC 9113 §3 and §9.2, not ported from another implementation.
46
66
  */
47
67
  import { Secret } from "nish:secret"
48
- import { H2_ERROR, H2_NEED_MORE, Http2Config, Http2Connection } from "nish/net/http2"
68
+ import { HttpDate } from "nish/net/http1-server"
69
+ import { H2_ERROR, H2_NEED_MORE, Http2Config, Http2Connection, Http2Pools } from "nish/net/http2"
49
70
  import { TlsServerConfig } from "nish/net/tls"
50
71
  import { TLS_RECORD_DONE, TLS_RECORD_STATE_OPEN, TLS_RECORD_WANT_WRITE } from "nish/net/tls/record-server"
51
72
  import { TlsTcpServer } from "nish/net/tls-tcp"
@@ -62,22 +83,57 @@ export const H2_ALPN: string = "h2"
62
83
  */
63
84
  export class Http2TlsServer {
64
85
  tls: TlsTcpServer
86
+ /** The caps every slot's connection is made under. */
87
+ config: Http2Config
88
+ /**
89
+ * The connections made so far, slot `k`'s at `k`: slot 0's, made by the
90
+ * constructor, and one for each slot accepted into since.
91
+ */
65
92
  connections: Http2Connection[]
93
+ /**
94
+ * The `date` every slot's responses carry, formatted once a second for all
95
+ * of them: each slot's `Http2Connection.date` is its bytes, refreshed by
96
+ * `connection` and `next`. `date.pin` fixes its clock.
97
+ */
98
+ date: HttpDate
99
+ /** The frame buffers every slot's connection borrows: one of each per slot at most, none made up front. */
100
+ readonly pools: Http2Pools
66
101
 
67
102
  /**
68
103
  * A pool of `size` slots, at least one, serving HTTP/2 under `config` over
69
- * TLS under `tlsConfig` on `listener`. Every slot's buffers are allocated
70
- * here.
104
+ * TLS under `tlsConfig` on `listener`. Only slot 0's connection is made
105
+ * here, so a config it refuses panics now, with its words; every other
106
+ * slot's is made by the first `accept` into it and kept. Frame buffers are
107
+ * borrowed from `pools` as a connection needs them.
71
108
  */
72
109
  constructor(tlsConfig: TlsServerConfig, config: Http2Config, listener: i32, size: i32) {
73
110
  this.tls = new TlsTcpServer(tlsConfig, listener, size)
111
+ this.config = config
74
112
  this.connections = []
75
- for (let k: i32 = 0; k < this.tls.size(); k++) {
76
- this.connections.push(new Http2Connection(config))
77
- }
113
+ this.date = new HttpDate()
114
+ // Formatted once now, so no slot ever sends the bytes before a first format.
115
+ const first: Http2Connection = new Http2Connection(config)
116
+ first.date = this.date.current()
117
+ this.connections.push(first)
118
+ // After slot 0's connection, so a config it refuses panics with its words.
119
+ this.pools = new Http2Pools("Http2TlsServer", config, this.tls.size())
120
+ first.pools = this.pools
78
121
  }
79
122
 
80
- /** How many slots the pool has. */
123
+ /** Makes the next slot's connection, on this pool's buffers and `date`. */
124
+ build(): void {
125
+ const conn: Http2Connection = new Http2Connection(this.config)
126
+ conn.date = this.date.current()
127
+ conn.pools = this.pools
128
+ this.connections.push(conn)
129
+ }
130
+
131
+ /** Whether `slot` holds a connection and its HTTP/2 side has been made. */
132
+ live(slot: i32): boolean {
133
+ return this.tls.holds(slot) && slot < toI32(this.connections.length)
134
+ }
135
+
136
+ /** How many slots the pool has, made or not. */
81
137
  size(): i32 {
82
138
  return this.tls.size()
83
139
  }
@@ -97,8 +153,12 @@ export class Http2TlsServer {
97
153
  return this.tls.fd(slot)
98
154
  }
99
155
 
100
- /** The HTTP/2 connection of `slot`; a slot out of range names the first. */
156
+ /**
157
+ * The HTTP/2 connection of `slot`, its `date` brought up to now. A slot
158
+ * never accepted into, or out of range, names slot 0's, which may be live.
159
+ */
101
160
  connection(slot: i32): Http2Connection {
161
+ this.date.current()
102
162
  const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : H2_TLS_ZERO
103
163
  return this.connections[at]
104
164
  }
@@ -111,33 +171,46 @@ export class Http2TlsServer {
111
171
  /**
112
172
  * Accepts the next waiting connection into a free slot, as
113
173
  * `TlsTcpServer.accept` does, and gives it a fresh HTTP/2 connection whose
114
- * SETTINGS go out once the handshake is done. Answers the slot, or what
115
- * `TlsTcpServer.accept` answers.
174
+ * SETTINGS go out once the handshake is done: the slot's own, restarted,
175
+ * or one made now if this is the first accept into it. Answers the slot,
176
+ * or what `TlsTcpServer.accept` answers.
116
177
  */
117
178
  accept(serverRandom: u8[], ephemeralPrivate: u8[]): i32 {
118
179
  const slot: i32 = this.tls.accept(serverRandom, ephemeralPrivate)
119
- if (slot >= 0 && slot < toI32(this.connections.length)) {
120
- this.connections[slot].restart()
180
+ if (slot < 0 || slot >= this.tls.size()) {
181
+ return slot
182
+ }
183
+ // `TlsTcpServer` hands out its lowest slots first, so this makes one
184
+ // connection, unless a program accepted on `tls` itself.
185
+ while (toI32(this.connections.length) <= slot) {
186
+ this.build()
121
187
  }
188
+ this.connections[slot].restart()
122
189
  return slot
123
190
  }
124
191
 
125
192
  /** The socket of `slot` is readable: TLS reads it, and what HTTP/2 has to send goes after. Answers the interest. */
126
193
  readable(slot: i32): i32 {
127
194
  this.tls.readable(slot)
128
- return this.flush(slot)
195
+ const wants: i32 = this.flush(slot)
196
+ this.rest(slot)
197
+ return wants
129
198
  }
130
199
 
131
200
  /** The socket of `slot` is writable. Answers the interest. */
132
201
  writable(slot: i32): i32 {
133
202
  this.tls.writable(slot)
134
- return this.flush(slot)
203
+ const wants: i32 = this.flush(slot)
204
+ this.rest(slot)
205
+ return wants
135
206
  }
136
207
 
137
208
  /** Signs `slot`'s CertificateVerify with `key`, as `TlsTcpServer.signP256` does. Answers the interest. */
138
209
  signP256(slot: i32, key: Secret<u8[]>): i32 {
139
210
  this.tls.signP256(slot, key)
140
- return this.flush(slot)
211
+ const wants: i32 = this.flush(slot)
212
+ this.rest(slot)
213
+ return wants
141
214
  }
142
215
 
143
216
  /** Whether `slot`'s handshake is done and chose anything but `h2`. */
@@ -151,9 +224,10 @@ export class Http2TlsServer {
151
224
  * slot, or one whose ALPN was not `h2`, answers `H2_ERROR`.
152
225
  */
153
226
  next(slot: i32): i32 {
154
- if (!this.holds(slot) || this.refused(slot)) {
227
+ if (!this.live(slot) || this.refused(slot)) {
155
228
  return H2_ERROR
156
229
  }
230
+ this.date.current()
157
231
  const conn: Http2Connection = this.connections[slot]
158
232
  // TLS decrypts straight into the connection's input, so a byte is
159
233
  // copied once on its way in.
@@ -177,6 +251,7 @@ export class Http2TlsServer {
177
251
  // the program's own `flush` after the loop.
178
252
  if (event === H2_NEED_MORE || event === H2_ERROR) {
179
253
  this.flush(slot)
254
+ this.rest(slot)
180
255
  }
181
256
  return event
182
257
  }
@@ -188,7 +263,7 @@ export class Http2TlsServer {
188
263
  * with the write bit while the connection still holds bytes.
189
264
  */
190
265
  flush(slot: i32): i32 {
191
- if (!this.holds(slot)) {
266
+ if (!this.live(slot)) {
192
267
  return TLS_RECORD_DONE
193
268
  }
194
269
  if (this.refused(slot)) {
@@ -209,8 +284,21 @@ export class Http2TlsServer {
209
284
  return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
210
285
  }
211
286
 
212
- /** Ends `slot`'s connection and frees the slot at once, as `TlsTcpServer.close` does. */
287
+ /**
288
+ * Ends `slot`'s connection and frees the slot at once, as
289
+ * `TlsTcpServer.close` does; the connection gives back every buffer it holds.
290
+ */
213
291
  close(slot: i32): void {
292
+ if (slot >= 0 && slot < toI32(this.connections.length)) {
293
+ this.connections[slot].drop()
294
+ }
214
295
  this.tls.close(slot)
215
296
  }
297
+
298
+ /** Gives back the buffers `slot`'s connection holds and has emptied, so an idle slot holds none. */
299
+ rest(slot: i32): void {
300
+ if (slot >= 0 && slot < toI32(this.connections.length)) {
301
+ this.connections[slot].rest()
302
+ }
303
+ }
216
304
  }