@amritk/nish-x86_64-linux 0.16.0 → 0.18.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 (58) hide show
  1. package/INSTALL.md +43 -34
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +50 -13
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-simd.c +203 -0
  11. package/runtime/runtime-wasm.c +12 -0
  12. package/runtime/runtime.c +66 -18
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +14 -12
  15. package/std/README.md +168 -36
  16. package/std/crypto/aes.ts +51 -8
  17. package/std/crypto/base64url.ts +25 -12
  18. package/std/crypto/chacha20poly1305.ts +43 -29
  19. package/std/crypto/ct.ts +6 -1
  20. package/std/crypto/hkdf.ts +364 -3
  21. package/std/crypto/hmac.ts +307 -7
  22. package/std/crypto/p256.ts +618 -34
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +3 -2
  25. package/std/crypto/x25519.ts +88 -36
  26. package/std/crypto/x509.ts +130 -47
  27. package/std/json.ts +297 -41
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +911 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +462 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +929 -0
  45. package/std/net/quic-packet.ts +1698 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1609 -0
  48. package/std/net/quic.ts +3578 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +414 -0
  54. package/std/net/tls.ts +1437 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
  58. package/std/text.ts +77 -0
@@ -0,0 +1,1389 @@
1
+ /**
2
+ * `nish/net/http1-server` — the server side of HTTP/1.1 (RFC 9112): a
3
+ * sans-IO connection that drives `nish/net/http1`'s parser, and two pools of
4
+ * them for a program that owns its readiness loop, one on `nish:net`'s plain
5
+ * TCP and one on `nish/net/tls-tcp` with ALPN `http/1.1`.
6
+ *
7
+ * const listener = tcpListen("0.0.0.0", 8080, 128);
8
+ * const server = new Http1Server(new Http1Config(), listener, 64);
9
+ * // when the listener is readable:
10
+ * const slot: i32 = server.accept();
11
+ * if (slot >= 0) { pollAdd(loop, server.fd(slot), 1, slot); }
12
+ * // when a connection's token comes back:
13
+ * let wants: i32 = (events & 5) !== 0 ? server.readable(slot) : server.writable(slot);
14
+ * let event: i32 = server.next(slot);
15
+ * while (event !== H1_NEED_MORE && event !== H1_ERROR) {
16
+ * const conn: Http1Connection = server.connection(slot);
17
+ * // H1_REQUEST: conn.parser.methodIs("GET"), conn.parser.targetIs("/"), ...
18
+ * // conn.respond(200, names, values, HTTP1_CHUNKED)
19
+ * // H1_BODY: conn.data[conn.dataStart .. conn.dataStart + conn.dataLength)
20
+ * // H1_END: conn.write(body, 0, n); conn.end()
21
+ * event = server.next(slot);
22
+ * }
23
+ * wants = server.flush(slot);
24
+ * if ((wants & H1_DONE) !== 0) { server.close(slot); }
25
+ * else { pollModify(loop, server.fd(slot), wants & 3, slot); }
26
+ *
27
+ * **Events.** `next()` answers `H1_REQUEST` (a request's head: the parser's
28
+ * spans and `...Is` methods read it), `H1_BODY` (some of its body, de-chunked,
29
+ * a window that stays valid until the next `next()`), `H1_END` (the request
30
+ * is complete), `H1_WRITE` (a write that was held back may be tried again),
31
+ * `H1_WS_MESSAGE` and `H1_WS_CLOSE` once the slot has switched to WebSocket,
32
+ * and `H1_ERROR`, final: the request broke a rule, the response that says so
33
+ * is queued, and the connection closes once it is sent. `H1_NEED_MORE` asks
34
+ * for more input, for the output to drain, or for the program to finish the
35
+ * response it owes.
36
+ *
37
+ * **One exchange at a time.** A request is answered before the next one on
38
+ * the connection is read: `respond`, any number of `write`s, `end`. The
39
+ * program may answer at once (before the body has arrived) or after
40
+ * `H1_END`; either way the request's body is read to its end before the next
41
+ * request starts, which is what keeps pipelined requests in order (§9.3.2).
42
+ * Pipelined bytes wait in the parser's buffer, and once it holds
43
+ * `inputLimit()` bytes the connection stops asking for input, so the client's
44
+ * TCP window closes: back-pressure on the way in.
45
+ *
46
+ * **Streaming both ways.** A request body arrives a chunk at a time, at most
47
+ * `chunkSize` bytes in each `H1_BODY`, whether it was sent with
48
+ * `Content-Length` or chunked. A response is written with a length or
49
+ * chunked, a `write` at a time into the output buffer; a `write` takes what
50
+ * fits and answers how much, and when it could not take everything the
51
+ * connection answers `H1_NEED_MORE` until the socket has drained half the
52
+ * output, then `H1_WRITE`. Until then it reads nothing, so the window the
53
+ * program is writing from stays valid: back-pressure on the way out. Nothing
54
+ * buffers a whole body.
55
+ *
56
+ * **What the server decides.** The framing field (`Content-Length` or
57
+ * `Transfer-Encoding: chunked`) and the `Connection` field are the server's;
58
+ * the program's fields may not hold either, nor `Upgrade`, `Keep-Alive` or
59
+ * `Content-Length`. A request that did not ask to keep the connection open
60
+ * gets `Connection: close` and the connection closes after it; an HTTP/1.0
61
+ * request that asked for keep-alive gets `Connection: keep-alive`; a chunked
62
+ * response to an HTTP/1.0 request is sent unframed and ends with the
63
+ * connection, since chunked is HTTP/1.1's. A response to HEAD, and a 204 or
64
+ * 304, carries no body whatever the program writes (RFC 9110 §9.3.2, §15.3.5,
65
+ * §15.4.5). A 1xx before the final response (100 Continue) is allowed; 101 is
66
+ * `acceptWebSocket`'s.
67
+ *
68
+ * **Refusals.** Every refusal of `nish/net/http1`'s parser — the smuggling
69
+ * shapes, a `Content-Length` with a `Transfer-Encoding`, two that disagree,
70
+ * a malformed chunk size — is answered with its status (400, 413, 414, 431,
71
+ * 501, 505), `Content-Length: 0` and `Connection: close`, and ends the
72
+ * connection; so does a malformed WebSocket frame, with its close code.
73
+ * `docs/security/http1.md` is the record.
74
+ *
75
+ * **WebSocket.** A request that asked to upgrade reaches the program like any
76
+ * other; `acceptWebSocket(protocol)` answers it with the 101 of
77
+ * `nish/net/websocket` when it is a valid opening handshake (RFC 6455 §4.2),
78
+ * and once its exchange is over the slot reads frames with a `WsDecoder`
79
+ * instead of requests, the bytes that followed the request included. A ping
80
+ * is answered with a pong and a close with a close, by the connection.
81
+ *
82
+ * **Caps are start-up numbers**, all in `Http1Config`: the request target,
83
+ * the header section and its field count, a request body, the chunk handed
84
+ * 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.
89
+ *
90
+ * Private helpers share the importing program's flat symbol namespace
91
+ * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
92
+ *
93
+ * Written from RFC 9112, RFC 9110 and RFC 6455, not ported from another
94
+ * implementation.
95
+ */
96
+ import { Secret } from "nish:secret"
97
+ import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
98
+ import {
99
+ HTTP1_BODY,
100
+ HTTP1_CHUNKED,
101
+ HTTP1_CLOSED,
102
+ HTTP1_END,
103
+ HTTP1_ERROR,
104
+ HTTP1_HEAD,
105
+ HTTP1_NO_BODY,
106
+ HTTP1_REFUSED,
107
+ HTTP1_UPGRADE,
108
+ Http1Parser,
109
+ http1BytesAre,
110
+ http1WriteChunk,
111
+ http1WriteLastChunk,
112
+ http1WriteResponseHead,
113
+ } from "nish/net/http1"
114
+ import {
115
+ WS_CLOSE,
116
+ WS_ERROR,
117
+ WS_MAX_CONTROL,
118
+ WS_MESSAGE,
119
+ WS_NEED_MORE,
120
+ WS_OP_CLOSE,
121
+ WS_OP_PONG,
122
+ WS_PING,
123
+ WsDecoder,
124
+ websocketClosePayload,
125
+ websocketFrameSize,
126
+ websocketRequestKey,
127
+ websocketUpgradeResponse,
128
+ websocketWriteFrame,
129
+ } from "nish/net/websocket"
130
+ import { TlsServerConfig } from "nish/net/tls"
131
+ import { TLS_RECORD_DONE, TLS_RECORD_STATE_OPEN, TLS_RECORD_WANT_WRITE } from "nish/net/tls/record-server"
132
+ import { TlsTcpServer } from "nish/net/tls-tcp"
133
+
134
+ /** `next`: nothing to hand over until more input arrives, the output drains, or the program answers. */
135
+ export const H1_NEED_MORE: i32 = 0
136
+ /** A request's head was read: `parser` holds it. */
137
+ export const H1_REQUEST: i32 = 1
138
+ /** Request body bytes: `data[dataStart .. dataStart + dataLength)`, valid until the next `next()`. */
139
+ export const H1_BODY: i32 = 2
140
+ /** The request is complete. */
141
+ export const H1_END: i32 = 3
142
+ /** A `write`, `respond`, `end` or `sendFrame` that answered `H1_AGAIN` may be tried again. */
143
+ export const H1_WRITE: i32 = 4
144
+ /** A WebSocket message: `opcode`, `data[dataStart .. dataStart + dataLength)`. */
145
+ export const H1_WS_MESSAGE: i32 = 5
146
+ /** The WebSocket peer closed with `status` (1005 for none); the close is answered. Final. */
147
+ export const H1_WS_CLOSE: i32 = 6
148
+ /** The input broke a rule; `status` is the HTTP status or WebSocket close code sent. Final. */
149
+ export const H1_ERROR: i32 = 7
150
+
151
+ /** Nothing fits yet: Linux's EAGAIN, as `nish:net` spells it. */
152
+ export const H1_AGAIN: i32 = -11
153
+ /** Something the server may not send: Linux's EINVAL. */
154
+ export const H1_INVALID: i32 = -22
155
+ /** No exchange the call belongs to: Linux's EPIPE. */
156
+ export const H1_CLOSED: i32 = -32
157
+ /** `accept`: every slot was busy, so the connection was accepted and closed. Linux's ENOBUFS. */
158
+ export const H1_POOL_FULL: i32 = -105
159
+
160
+ /** Interest bits, the same as `nish/net/tls/record-server`'s: the low two are `pollModify`'s events. */
161
+ export const H1_WANT_READ: i32 = 1
162
+ export const H1_WANT_WRITE: i32 = 2
163
+ /** The slot's connection is over and every byte is sent: `close` it. */
164
+ export const H1_DONE: i32 = 16
165
+
166
+ /** The ALPN protocol identifier of HTTP/1.1 (RFC 7301 §6). */
167
+ export const H1_ALPN: string = "http/1.1"
168
+
169
+ /** A typed zero: a bare literal is an `f64` under `--number-mode f64`. */
170
+ const H1_ZERO: i32 = 0
171
+
172
+ /** A free slot's descriptor. */
173
+ const H1_FREE: i32 = -1
174
+
175
+ /** The address form's length, from `nish:net`. */
176
+ const H1_ADDRESS_SIZE: i32 = 18
177
+
178
+ /**
179
+ * Output kept free of response bytes and frames, so that what the connection
180
+ * writes on its own — an error response, a pong, a close frame — always fits.
181
+ */
182
+ const H1_CONTROL_RESERVE: i32 = 256
183
+
184
+ /** The most a chunk's framing adds: eight hex digits and two CRLFs. */
185
+ const H1_CHUNK_OVERHEAD: i32 = 12
186
+
187
+ /** The longest request line a head can need beyond the target: the method, two spaces, the version, CRLF. */
188
+ const H1_LINE_OVERHEAD: i32 = 44
189
+
190
+ /** The longest WebSocket frame header, which `WsDecoder` buffers with its whole frame: 2, 8 of length, 4 of mask. */
191
+ const H1_FRAME_HEADER: i32 = 14
192
+
193
+ // The connection's states.
194
+ const H1_S_HTTP: i32 = 0
195
+ const H1_S_WEBSOCKET: i32 = 1
196
+ /** Sending what is left, then done. */
197
+ const H1_S_CLOSING: i32 = 2
198
+ /** `H1_ERROR` was answered; sending what is left, then done. */
199
+ const H1_S_FAILED: i32 = 3
200
+
201
+ // Where the current exchange's response stands.
202
+ const H1_R_NONE: i32 = 0
203
+ const H1_R_BODY: i32 = 1
204
+ const H1_R_DONE: i32 = 2
205
+
206
+ /** Panics unless `value` is in `low .. high`: a cap no connection could work with is the program's mistake. */
207
+ const http1ServerCheckCap = (name: string, value: i32, low: i32, high: i32): void => {
208
+ if (value < low || value > high) {
209
+ panic(`Http1Config.${name}: ${value} is outside ${low} to ${high}`)
210
+ }
211
+ }
212
+
213
+ /** Panics unless `buf[off .. off + len)` is inside `buf`: a window past its buffer is the caller's mistake. */
214
+ const http1ServerCheckWindow = (what: string, buf: u8[], off: i32, len: i32): void => {
215
+ const size: i32 = toI32(buf.length)
216
+ if (off < 0 || len < 0 || off > size || len > size - off) {
217
+ panic(`${what}: the window is outside the buffer`)
218
+ }
219
+ }
220
+
221
+ /** Copies `src[off .. off + n)` to `out` at `at`, which the caller has made sure holds it, and answers where it ends. */
222
+ const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 => {
223
+ for (let k: i32 = 0; k < n && at + k < toI32(out.length) && off + k < toI32(src.length); k++) {
224
+ if (at + k >= 0 && off + k >= 0) {
225
+ out[at + k] = src[off + k]
226
+ }
227
+ }
228
+ return at + n
229
+ }
230
+
231
+ /**
232
+ * The caps of every connection made from it, fixed when the connection is
233
+ * made. Each is a number the program sets once, at start-up.
234
+ */
235
+ export class Http1Config {
236
+ /** The longest request target; past it, 414. With `maxHeaderBytes`, at most 2^30 − 44, the most a head buffer holds. */
237
+ maxTarget: i32 = 8192
238
+ /** The longest header section, and trailer section; past it, 431. */
239
+ maxHeaderBytes: i32 = 16384
240
+ /** The most fields in a header section, trailers counted; past it, 431. */
241
+ maxHeaders: i32 = 100
242
+ /** The longest request body, declared or de-chunked; past it, 413. */
243
+ maxBody: i32 = 1073741824
244
+ /** The most body bytes one `H1_BODY` hands over, and how much is read from the socket at once. */
245
+ chunkSize: i32 = 16384
246
+ /** The output buffer, in bytes: the most of a response waiting for the socket. */
247
+ outputSize: i32 = 65536
248
+ /** The longest WebSocket message, its fragments together; past it, close 1009. At most 2^30 − 14, so a whole frame fits the decoder's input. */
249
+ maxMessage: i32 = 1048576
250
+ /** Milliseconds without a byte read or written after which `expire` closes a slot. */
251
+ idleTimeout: i32 = 60000
252
+ }
253
+
254
+ /** The reason phrase a status is sent with; RFC 9112 §4 lets a client ignore it. */
255
+ const http1ServerReason = (status: i32): string => {
256
+ switch (status) {
257
+ case 100:
258
+ return "Continue"
259
+ case 101:
260
+ return "Switching Protocols"
261
+ case 200:
262
+ return "OK"
263
+ case 201:
264
+ return "Created"
265
+ case 204:
266
+ return "No Content"
267
+ case 206:
268
+ return "Partial Content"
269
+ case 301:
270
+ return "Moved Permanently"
271
+ case 302:
272
+ return "Found"
273
+ case 304:
274
+ return "Not Modified"
275
+ case 400:
276
+ return "Bad Request"
277
+ case 403:
278
+ return "Forbidden"
279
+ case 404:
280
+ return "Not Found"
281
+ case 405:
282
+ return "Method Not Allowed"
283
+ case 408:
284
+ return "Request Timeout"
285
+ case 413:
286
+ return "Content Too Large"
287
+ case 414:
288
+ return "URI Too Long"
289
+ case 426:
290
+ return "Upgrade Required"
291
+ case 431:
292
+ return "Request Header Fields Too Large"
293
+ case 500:
294
+ return "Internal Server Error"
295
+ case 501:
296
+ return "Not Implemented"
297
+ case 503:
298
+ return "Service Unavailable"
299
+ case 505:
300
+ return "HTTP Version Not Supported"
301
+ default:
302
+ return "Status"
303
+ }
304
+ }
305
+
306
+ /** Whether `name` is one of the fields the server decides and a program may not send. */
307
+ const http1ServerReserved = (name: u8[]): boolean =>
308
+ http1BytesAre(name, "connection") ||
309
+ http1BytesAre(name, "upgrade") ||
310
+ http1BytesAre(name, "keep-alive") ||
311
+ http1BytesAre(name, "transfer-encoding") ||
312
+ http1BytesAre(name, "content-length")
313
+
314
+ /**
315
+ * One connection's HTTP/1.1 server, sans-IO: bytes in through `feed`, events
316
+ * out of `next`, and the response the program writes in
317
+ * `output[outputStart .. outputEnd)` for its carrier to send.
318
+ */
319
+ export class Http1Connection {
320
+ // References, then 64-bit fields, then 32-bit ones, then flags: the order
321
+ // the layout packs without padding.
322
+ config: Http1Config
323
+ /** The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is the last `H1_REQUEST`'s. */
324
+ parser: Http1Parser
325
+ /** The frame decoder, once the slot has switched to WebSocket. */
326
+ ws: WsDecoder
327
+ /** Bytes to send are `output[outputStart .. outputEnd)`. */
328
+ output: u8[]
329
+ /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
330
+ data: u8[]
331
+ /** A ping's payload, kept until its pong fits. */
332
+ pong: u8[]
333
+ /** No fields, for the responses the connection writes itself: made once. */
334
+ noFields: u8[][]
335
+ /** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
336
+ lastActive: i64 = 0
337
+ outputStart: i32 = 0
338
+ outputEnd: i32 = 0
339
+ dataStart: i32 = 0
340
+ dataLength: i32 = 0
341
+ /** `H1_WS_MESSAGE`'s opcode: text or binary. */
342
+ opcode: i32 = 0
343
+ /** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
344
+ status: i32 = 0
345
+ /** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
346
+ bufferLimit: i32 = 0
347
+ pongLength: i32 = 0
348
+ state: i32 = 0
349
+ response: i32 = 0
350
+ /**
351
+ * How the response's body is framed: a length (0 or more), `HTTP1_CHUNKED`,
352
+ * or `HTTP1_NO_BODY` for one that ends with the connection.
353
+ */
354
+ responseFraming: i32 = 0
355
+ /** A response with a length: the bytes it still owes. */
356
+ responseLeft: i32 = 0
357
+ /** The response carries no body (HEAD, 204, 304): what is written is dropped. */
358
+ responseEmpty: boolean = false
359
+ /** A request's head has been read and its exchange is not over. */
360
+ requestOpen: boolean = false
361
+ /** The request's body has been read to its end. */
362
+ requestEnded: boolean = false
363
+ /** The connection closes once this exchange is over. */
364
+ closeAfter: boolean = false
365
+ /** The program accepted the request's WebSocket handshake. */
366
+ upgraded: boolean = false
367
+ /** A write was held back; `next` answers nothing but `H1_WRITE` until there is room. */
368
+ writeHeld: boolean = false
369
+ pongPending: boolean = false
370
+ /** The peer's byte stream has ended. */
371
+ inputEnded: boolean = false
372
+
373
+ /** A connection under `config`. A cap outside what the connection can work with panics. */
374
+ constructor(config: Http1Config) {
375
+ const big: i32 = 1073741824
376
+ const one: i32 = 1
377
+ const smallest: i32 = 64
378
+ const output: i32 = 1024
379
+ http1ServerCheckCap("maxTarget", config.maxTarget, one, big)
380
+ http1ServerCheckCap("maxHeaderBytes", config.maxHeaderBytes, smallest, big)
381
+ http1ServerCheckCap("maxHeaders", config.maxHeaders, one, big)
382
+ http1ServerCheckCap("maxBody", config.maxBody, H1_ZERO, big)
383
+ http1ServerCheckCap("chunkSize", config.chunkSize, one, big)
384
+ http1ServerCheckCap("outputSize", config.outputSize, output, big)
385
+ http1ServerCheckCap("maxMessage", config.maxMessage, H1_ZERO, big - H1_FRAME_HEADER)
386
+ http1ServerCheckCap("idleTimeout", config.idleTimeout, one, big)
387
+ // The buffers the peer's bytes fill grow by doubling and stop at 2^30
388
+ // (`Http1Parser`'s input and head, `WsDecoder`'s input), so a cap whose
389
+ // buffer could need more is refused here rather than reached by a peer:
390
+ // the head holds the request line and the header section together, and
391
+ // the input never holds more than the larger of the two lines or a chunk.
392
+ http1ServerCheckCap(
393
+ "maxTarget + maxHeaderBytes",
394
+ config.maxTarget + config.maxHeaderBytes,
395
+ one,
396
+ big - H1_LINE_OVERHEAD
397
+ )
398
+ this.config = config
399
+ this.parser = new Http1Parser(config.maxTarget, config.maxHeaderBytes, config.maxHeaders, config.maxBody)
400
+ this.parser.keepText = false
401
+ this.parser.maxChunk = config.chunkSize
402
+ this.ws = new WsDecoder(true, config.maxMessage)
403
+ this.output = new Array<u8>(config.outputSize)
404
+ this.data = this.output
405
+ this.pong = new Array<u8>(WS_MAX_CONTROL)
406
+ this.noFields = []
407
+ // A line must fit whole before the parser can read it, so the buffer
408
+ // holds the longest request line or field line however small a chunk is.
409
+ let limit: i32 = config.chunkSize
410
+ if (config.maxHeaderBytes + 2 > limit) {
411
+ limit = config.maxHeaderBytes + 2
412
+ }
413
+ if (config.maxTarget + H1_LINE_OVERHEAD > limit) {
414
+ limit = config.maxTarget + H1_LINE_OVERHEAD
415
+ }
416
+ this.bufferLimit = limit
417
+ this.restart()
418
+ }
419
+
420
+ /** Puts the connection back as the constructor left it, for the next peer. It allocates nothing. */
421
+ restart(): void {
422
+ this.parser.restart()
423
+ this.ws.reset()
424
+ this.outputStart = 0
425
+ this.outputEnd = 0
426
+ this.data = this.output
427
+ this.dataStart = 0
428
+ this.dataLength = 0
429
+ this.opcode = 0
430
+ this.status = 0
431
+ this.pongLength = 0
432
+ this.state = H1_S_HTTP
433
+ this.response = H1_R_NONE
434
+ this.responseFraming = 0
435
+ this.responseLeft = 0
436
+ this.responseEmpty = false
437
+ this.requestOpen = false
438
+ this.requestEnded = false
439
+ this.closeAfter = false
440
+ this.upgraded = false
441
+ this.writeHeld = false
442
+ this.pongPending = false
443
+ this.inputEnded = false
444
+ }
445
+
446
+ // ---- Bytes in and out ---------------------------------------------------------
447
+
448
+ /** The most input the connection holds unread: what `inputRoom` counts against. */
449
+ inputLimit(): i32 {
450
+ return this.state === H1_S_WEBSOCKET ? this.config.maxMessage + 14 : this.bufferLimit
451
+ }
452
+
453
+ /** How many bytes `feed` would take now: none while a write is held back or once the connection is closing. */
454
+ inputRoom(): i32 {
455
+ if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
456
+ return 0
457
+ }
458
+ const held: i32 = this.state === H1_S_WEBSOCKET ? this.ws.end - this.ws.start : this.parser.buffered()
459
+ const room: i32 = this.inputLimit() - held
460
+ return room > 0 ? room : 0
461
+ }
462
+
463
+ /**
464
+ * Takes as much of `buf[off .. off + len)` as `inputRoom` allows and
465
+ * answers how much; the rest is the caller's to feed again later. A window
466
+ * outside `buf` panics.
467
+ */
468
+ feed(buf: u8[], off: i32, len: i32): i32 {
469
+ http1ServerCheckWindow("Http1Connection.feed", buf, off, len)
470
+ const room: i32 = this.inputRoom()
471
+ const n: i32 = len < room ? len : room
472
+ if (n <= 0) {
473
+ return 0
474
+ }
475
+ if (this.state === H1_S_WEBSOCKET) {
476
+ this.ws.feed(buf, off, n)
477
+ } else {
478
+ this.parser.feed(buf, off, n)
479
+ }
480
+ return n
481
+ }
482
+
483
+ /** The peer's byte stream has ended: once what it sent is answered, the connection is done. */
484
+ endInput(): void {
485
+ this.inputEnded = true
486
+ }
487
+
488
+ /** The socket failed: nothing more is sent or read, and the connection is done. */
489
+ abort(): void {
490
+ this.state = H1_S_FAILED
491
+ this.outputStart = 0
492
+ this.outputEnd = 0
493
+ this.pongPending = false
494
+ this.writeHeld = false
495
+ }
496
+
497
+ /** Marks the first `n` bytes of the output sent. */
498
+ consume(n: i32): void {
499
+ const pending: i32 = this.outputEnd - this.outputStart
500
+ this.outputStart = this.outputStart + (n < pending ? n : pending)
501
+ if (this.outputStart === this.outputEnd) {
502
+ this.outputStart = 0
503
+ this.outputEnd = 0
504
+ }
505
+ }
506
+
507
+ /** Whether the output holds bytes to send. */
508
+ wantsWrite(): boolean {
509
+ return this.outputEnd > this.outputStart
510
+ }
511
+
512
+ /** Whether the connection is over and everything is sent: the carrier closes it. */
513
+ isDone(): boolean {
514
+ return (
515
+ (this.state === H1_S_CLOSING || this.state === H1_S_FAILED) && !this.wantsWrite() && !this.pongPending
516
+ )
517
+ }
518
+
519
+ /** The room at the end of the output, once what was sent is moved out of the way. */
520
+ room(): i32 {
521
+ const output: u8[] = this.output
522
+ const live: i32 = this.outputEnd - this.outputStart
523
+ if (this.outputStart > 0) {
524
+ for (
525
+ let k: i32 = 0;
526
+ k < live && k < toI32(output.length) && this.outputStart + k < toI32(output.length);
527
+ k++
528
+ ) {
529
+ output[k] = output[this.outputStart + k]
530
+ }
531
+ this.outputStart = 0
532
+ this.outputEnd = live
533
+ }
534
+ return toI32(output.length) - this.outputEnd
535
+ }
536
+
537
+ /** The room a response or a frame may use: all but the control reserve. */
538
+ bodyRoom(): i32 {
539
+ const r: i32 = this.room() - H1_CONTROL_RESERVE
540
+ return r > 0 ? r : 0
541
+ }
542
+
543
+ // ---- Events -------------------------------------------------------------------
544
+
545
+ /** The next event; see the module comment. */
546
+ next(): i32 {
547
+ if (this.state === H1_S_FAILED) {
548
+ return H1_ERROR
549
+ }
550
+ if (this.writeHeld) {
551
+ // Half the output free before the program is asked to write again, so
552
+ // 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) {
554
+ return H1_NEED_MORE
555
+ }
556
+ this.writeHeld = false
557
+ return H1_WRITE
558
+ }
559
+ if (this.state === H1_S_CLOSING) {
560
+ return H1_NEED_MORE
561
+ }
562
+ if (this.state === H1_S_WEBSOCKET) {
563
+ return this.nextFrame()
564
+ }
565
+ return this.nextRequest()
566
+ }
567
+
568
+ /** `next` while the connection speaks HTTP. */
569
+ nextRequest(): i32 {
570
+ while (true) {
571
+ if (this.requestEnded) {
572
+ if (this.response !== H1_R_DONE) {
573
+ return H1_NEED_MORE
574
+ }
575
+ // The exchange is over: the next request, a WebSocket, or the close.
576
+ this.requestOpen = false
577
+ this.requestEnded = false
578
+ this.response = H1_R_NONE
579
+ if (this.upgraded) {
580
+ const p: Http1Parser = this.parser
581
+ this.ws.reset()
582
+ this.ws.feed(p.buf, p.start, p.buffered())
583
+ this.state = H1_S_WEBSOCKET
584
+ return this.nextFrame()
585
+ }
586
+ if (this.closeAfter) {
587
+ this.state = H1_S_CLOSING
588
+ return H1_NEED_MORE
589
+ }
590
+ }
591
+ const event: i32 = this.parser.next()
592
+ if (event === HTTP1_HEAD) {
593
+ this.requestOpen = true
594
+ this.response = H1_R_NONE
595
+ this.closeAfter = !this.parser.keepAlive
596
+ this.upgraded = false
597
+ return H1_REQUEST
598
+ }
599
+ if (event === HTTP1_BODY) {
600
+ this.data = this.parser.body
601
+ this.dataStart = this.parser.bodyOff
602
+ this.dataLength = this.parser.bodyLen
603
+ return H1_BODY
604
+ }
605
+ if (event === HTTP1_END) {
606
+ this.requestEnded = true
607
+ return H1_END
608
+ }
609
+ if (event === HTTP1_UPGRADE) {
610
+ // Reached only once a response that did not switch is over.
611
+ this.parser.declineUpgrade()
612
+ } else if (event === HTTP1_ERROR) {
613
+ return this.fail(this.parser.status)
614
+ } else if (event === HTTP1_CLOSED) {
615
+ this.state = H1_S_CLOSING
616
+ return H1_NEED_MORE
617
+ } else {
618
+ // HTTP1_NEED_MORE. A request cut off by the end of the stream is
619
+ // over: there is no one left to answer.
620
+ if (this.inputEnded) {
621
+ this.state = H1_S_CLOSING
622
+ }
623
+ return H1_NEED_MORE
624
+ }
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Refuses the request with `status`: the response that says so, when none
630
+ * has started, and the close after it. Answers `H1_ERROR`.
631
+ */
632
+ fail(status: i32): i32 {
633
+ this.status = status
634
+ if (this.response === H1_R_NONE) {
635
+ // The control reserve keeps room for this whatever the output holds.
636
+ this.room()
637
+ const end: i32 = http1WriteResponseHead(
638
+ this.output,
639
+ this.outputEnd,
640
+ status,
641
+ http1ServerReason(status),
642
+ this.noFields,
643
+ this.noFields,
644
+ H1_ZERO,
645
+ "close"
646
+ )
647
+ if (end >= 0) {
648
+ this.outputEnd = end
649
+ }
650
+ }
651
+ this.state = H1_S_FAILED
652
+ this.writeHeld = false
653
+ return H1_ERROR
654
+ }
655
+
656
+ /** `next` once the slot has switched to WebSocket. */
657
+ nextFrame(): i32 {
658
+ while (true) {
659
+ if (this.pongPending && !this.sendPong()) {
660
+ return H1_NEED_MORE
661
+ }
662
+ const event: i32 = this.ws.next()
663
+ if (event === WS_MESSAGE) {
664
+ this.opcode = this.ws.opcode
665
+ this.data = this.ws.data
666
+ this.dataStart = 0
667
+ this.dataLength = this.ws.dataLen
668
+ return H1_WS_MESSAGE
669
+ }
670
+ if (event === WS_PING) {
671
+ const n: i32 = this.ws.dataLen
672
+ for (let k: i32 = 0; k < n && k < toI32(this.pong.length) && k < toI32(this.ws.data.length); k++) {
673
+ this.pong[k] = this.ws.data[k]
674
+ }
675
+ this.pongLength = n
676
+ this.pongPending = true
677
+ } else if (event === WS_CLOSE) {
678
+ this.status = this.ws.closeCode
679
+ this.sendClose(this.ws.closeCode)
680
+ this.state = H1_S_CLOSING
681
+ return H1_WS_CLOSE
682
+ } else if (event === WS_ERROR) {
683
+ this.status = this.ws.closeCode
684
+ this.sendClose(this.ws.closeCode)
685
+ this.state = H1_S_FAILED
686
+ return H1_ERROR
687
+ } else if (event === WS_NEED_MORE) {
688
+ if (this.inputEnded) {
689
+ this.state = H1_S_CLOSING
690
+ }
691
+ return H1_NEED_MORE
692
+ }
693
+ // A pong needs no answer: read on.
694
+ }
695
+ }
696
+
697
+ /** Writes the pending pong if it fits, from the control reserve; answers whether it went. */
698
+ sendPong(): boolean {
699
+ this.room()
700
+ const end: i32 = websocketWriteFrame(
701
+ this.output,
702
+ this.outputEnd,
703
+ true,
704
+ WS_OP_PONG,
705
+ this.pong,
706
+ H1_ZERO,
707
+ this.pongLength,
708
+ null
709
+ )
710
+ if (end < 0) {
711
+ return false
712
+ }
713
+ this.outputEnd = end
714
+ this.pongPending = false
715
+ return true
716
+ }
717
+
718
+ /** Writes a close frame with `code` (none for 1005), from the control reserve. */
719
+ sendClose(code: i32): void {
720
+ this.room()
721
+ const payload: u8[] | null = websocketClosePayload(code, "")
722
+ // A code that may not be sent (1005, for a close that had none) is
723
+ // answered with an empty close.
724
+ const body: u8[] = payload === null ? this.pong : payload
725
+ const n: i32 = payload === null ? H1_ZERO : toI32(body.length)
726
+ const end: i32 = websocketWriteFrame(
727
+ this.output,
728
+ this.outputEnd,
729
+ true,
730
+ WS_OP_CLOSE,
731
+ body,
732
+ H1_ZERO,
733
+ n,
734
+ null
735
+ )
736
+ if (end >= 0) {
737
+ this.outputEnd = end
738
+ }
739
+ }
740
+
741
+ // ---- The response -------------------------------------------------------------
742
+
743
+ /** Whether the program may answer the current request now. */
744
+ answerable(): boolean {
745
+ return this.state === H1_S_HTTP && this.requestOpen && this.response === H1_R_NONE
746
+ }
747
+
748
+ /**
749
+ * Answers the current request with `status` and the fields `names[k]`:
750
+ * `values[k]`, and the framing `bodyLength` asks for: a length, or
751
+ * `HTTP1_CHUNKED`, or `HTTP1_NO_BODY` for a 1xx, a 204 or a 304. A 1xx
752
+ * (but 101) is interim and the final status follows it. Answers 0;
753
+ * `H1_INVALID` for a status outside 100 to 599 or 101, a reserved field, a
754
+ * name or value `http1WriteResponseHead` refuses, or a head larger than the
755
+ * output; `H1_AGAIN` when the output has no room for it yet (`H1_WRITE`
756
+ * says when to try again); `H1_CLOSED` with no request to answer.
757
+ */
758
+ respond(status: i32, names: u8[][], values: u8[][], bodyLength: i32): i32 {
759
+ if (!this.answerable()) {
760
+ return H1_CLOSED
761
+ }
762
+ if (status < 100 || status > 599 || status === 101) {
763
+ return H1_INVALID
764
+ }
765
+ for (const name of names) {
766
+ if (http1ServerReserved(name)) {
767
+ return H1_INVALID
768
+ }
769
+ }
770
+ const interim: boolean = status < 200
771
+ const empty: boolean = interim || status === 204 || status === 304 || this.parser.methodIs("HEAD")
772
+ if (!empty && bodyLength === HTTP1_NO_BODY) {
773
+ return H1_INVALID
774
+ }
775
+ let framing: i32 = bodyLength
776
+ let close: boolean = this.closeAfter
777
+ // Chunked is HTTP/1.1's: an HTTP/1.0 client reads the body to the close.
778
+ if (!empty && bodyLength === HTTP1_CHUNKED && this.parser.minor === 0) {
779
+ framing = HTTP1_NO_BODY
780
+ close = true
781
+ }
782
+ let connection: string = ""
783
+ if (!interim && close) {
784
+ connection = "close"
785
+ } else if (!interim && this.parser.minor === 0) {
786
+ connection = "keep-alive"
787
+ }
788
+ const room: i32 = this.bodyRoom()
789
+ const end: i32 = http1WriteResponseHead(
790
+ this.output,
791
+ this.outputEnd,
792
+ status,
793
+ http1ServerReason(status),
794
+ names,
795
+ values,
796
+ framing,
797
+ connection
798
+ )
799
+ if (end === HTTP1_REFUSED) {
800
+ return H1_INVALID
801
+ }
802
+ if (end < 0 || end - this.outputEnd > room) {
803
+ // 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) {
805
+ return H1_INVALID
806
+ }
807
+ if (this.outputEnd === 0) {
808
+ return H1_INVALID
809
+ }
810
+ this.writeHeld = true
811
+ return H1_AGAIN
812
+ }
813
+ this.outputEnd = end
814
+ if (interim) {
815
+ return 0
816
+ }
817
+ this.closeAfter = close
818
+ this.response = H1_R_BODY
819
+ this.responseFraming = framing
820
+ this.responseLeft = framing >= 0 ? framing : 0
821
+ this.responseEmpty = empty
822
+ return 0
823
+ }
824
+
825
+ /**
826
+ * Writes `buf[off .. off + len)` of the response body, as much as the output
827
+ * has room for, and answers how much: a chunk of a chunked body, or bytes
828
+ * of one with a length (more than it still owes is `H1_INVALID`). When not
829
+ * everything was taken, `H1_WRITE` says when to try the rest; `H1_AGAIN`
830
+ * when nothing was. A response with no body takes everything and sends
831
+ * nothing. `H1_CLOSED` with no response under way. A window outside `buf`
832
+ * panics.
833
+ */
834
+ write(buf: u8[], off: i32, len: i32): i32 {
835
+ http1ServerCheckWindow("Http1Connection.write", buf, off, len)
836
+ if (this.response !== H1_R_BODY || this.state !== H1_S_HTTP) {
837
+ return H1_CLOSED
838
+ }
839
+ if (this.responseEmpty || len === 0) {
840
+ return len
841
+ }
842
+ const chunked: boolean = this.responseFraming === HTTP1_CHUNKED
843
+ if (this.responseFraming >= 0 && len > this.responseLeft) {
844
+ return H1_INVALID
845
+ }
846
+ const room: i32 = this.bodyRoom()
847
+ let take: i32 = len
848
+ if (chunked) {
849
+ if (take > room - H1_CHUNK_OVERHEAD) {
850
+ take = room - H1_CHUNK_OVERHEAD
851
+ }
852
+ } else if (take > room) {
853
+ take = room
854
+ }
855
+ if (take <= 0) {
856
+ this.writeHeld = true
857
+ return H1_AGAIN
858
+ }
859
+ if (chunked) {
860
+ this.outputEnd = http1WriteChunk(this.output, this.outputEnd, buf, off, take)
861
+ } else {
862
+ this.outputEnd = http1ServerCopy(this.output, this.outputEnd, buf, off, take)
863
+ if (this.responseFraming >= 0) {
864
+ this.responseLeft = this.responseLeft - take
865
+ }
866
+ }
867
+ if (take < len) {
868
+ this.writeHeld = true
869
+ }
870
+ return take
871
+ }
872
+
873
+ /**
874
+ * Ends the response: the last chunk of a chunked body. Answers 0;
875
+ * `H1_INVALID` for a response with a length that still owes bytes;
876
+ * `H1_AGAIN` when the last chunk does not fit yet; `H1_CLOSED` with no
877
+ * response under way.
878
+ */
879
+ end(): i32 {
880
+ if (this.response !== H1_R_BODY || this.state !== H1_S_HTTP) {
881
+ return H1_CLOSED
882
+ }
883
+ if (this.responseEmpty) {
884
+ this.response = H1_R_DONE
885
+ return 0
886
+ }
887
+ if (this.responseFraming >= 0 && this.responseLeft > 0) {
888
+ return H1_INVALID
889
+ }
890
+ if (this.responseFraming === HTTP1_CHUNKED) {
891
+ // The last chunk comes out of the body's room, not the reserve.
892
+ if (this.bodyRoom() < 5) {
893
+ this.writeHeld = true
894
+ return H1_AGAIN
895
+ }
896
+ this.outputEnd = http1WriteLastChunk(this.output, this.outputEnd)
897
+ }
898
+ this.response = H1_R_DONE
899
+ // A response with neither a length nor chunks ends with the connection.
900
+ if (this.responseFraming === HTTP1_NO_BODY) {
901
+ this.closeAfter = true
902
+ }
903
+ return 0
904
+ }
905
+
906
+ // ---- WebSocket ----------------------------------------------------------------
907
+
908
+ /**
909
+ * Accepts the current request's WebSocket opening handshake (RFC 6455
910
+ * §4.2.2) with `protocol`, empty for none: the 101 is queued, and once the
911
+ * request has ended the slot reads frames. Answers 0; `H1_INVALID` when the
912
+ * request is not a valid opening handshake (the program answers 400, or 426
913
+ * for the wrong version) or `protocol` is not a token; `H1_AGAIN` when the
914
+ * output has no room yet; `H1_CLOSED` with no request to answer.
915
+ */
916
+ acceptWebSocket(protocol: string): i32 {
917
+ if (!this.answerable()) {
918
+ return H1_CLOSED
919
+ }
920
+ const key: string | null = websocketRequestKey(this.parser)
921
+ if (key === null) {
922
+ return H1_INVALID
923
+ }
924
+ const head: u8[] | null = websocketUpgradeResponse(key, protocol)
925
+ if (head === null) {
926
+ return H1_INVALID
927
+ }
928
+ const n: i32 = toI32(head.length)
929
+ if (n > this.bodyRoom()) {
930
+ this.writeHeld = true
931
+ return H1_AGAIN
932
+ }
933
+ this.outputEnd = http1ServerCopy(this.output, this.outputEnd, head, H1_ZERO, n)
934
+ this.response = H1_R_DONE
935
+ this.upgraded = true
936
+ this.closeAfter = false
937
+ return 0
938
+ }
939
+
940
+ /**
941
+ * Sends one WebSocket frame, unmasked as a server's are (§5.1): a whole
942
+ * text or binary message, a fragment of one (`fin` false, then
943
+ * continuations), a ping or a pong. Answers 0; `H1_INVALID` for a frame
944
+ * `websocketWriteFrame` refuses, a close (that is `closeWebSocket`), or one
945
+ * larger than the output; `H1_AGAIN` when it does not fit yet; `H1_CLOSED`
946
+ * when the slot is not an open WebSocket. A window outside `buf` panics.
947
+ */
948
+ sendFrame(fin: boolean, opcode: i32, buf: u8[], off: i32, len: i32): i32 {
949
+ http1ServerCheckWindow("Http1Connection.sendFrame", buf, off, len)
950
+ if (this.state !== H1_S_WEBSOCKET) {
951
+ return H1_CLOSED
952
+ }
953
+ if (opcode === WS_OP_CLOSE || len > toI32(this.output.length) - H1_CONTROL_RESERVE - 14) {
954
+ return H1_INVALID
955
+ }
956
+ if (websocketFrameSize(len, false) > this.bodyRoom()) {
957
+ this.writeHeld = true
958
+ return H1_AGAIN
959
+ }
960
+ const end: i32 = websocketWriteFrame(this.output, this.outputEnd, fin, opcode, buf, off, len, null)
961
+ if (end < 0) {
962
+ return H1_INVALID
963
+ }
964
+ this.outputEnd = end
965
+ return 0
966
+ }
967
+
968
+ /**
969
+ * Starts the closing handshake (§5.5.1, §7.1.2) with `code`: a close frame,
970
+ * then the connection is done once it is sent. Answers 0, `H1_INVALID` for a
971
+ * code that may not be sent, or `H1_CLOSED` when the slot is not an open
972
+ * WebSocket.
973
+ */
974
+ closeWebSocket(code: i32): i32 {
975
+ if (this.state !== H1_S_WEBSOCKET) {
976
+ return H1_CLOSED
977
+ }
978
+ if (websocketClosePayload(code, "") === null) {
979
+ return H1_INVALID
980
+ }
981
+ this.sendClose(code)
982
+ this.state = H1_S_CLOSING
983
+ return 0
984
+ }
985
+
986
+ /** Whether the slot has switched to WebSocket and is open. */
987
+ isWebSocket(): boolean {
988
+ return this.state === H1_S_WEBSOCKET
989
+ }
990
+
991
+ /** Whether nothing has moved for longer than the idle timeout, at `now` (`monotonicNanos`). */
992
+ idle(now: i64): boolean {
993
+ return now - this.lastActive > toI64(this.config.idleTimeout) * toI64(1000000)
994
+ }
995
+ }
996
+
997
+ // ---- Carriers -------------------------------------------------------------------
998
+
999
+ /**
1000
+ * A pool of HTTP/1.1 connections over plain TCP on one listening socket.
1001
+ * `slot`s are the indices `accept` answers; a slot is the program's from
1002
+ * `accept` until it calls `close`, and its descriptor is `fd(slot)`.
1003
+ */
1004
+ export class Http1Server {
1005
+ config: Http1Config
1006
+ connections: Http1Connection[]
1007
+ /** Each slot's descriptor, or -1 while it is free. */
1008
+ fds: i32[]
1009
+ /** Where the socket is read into before the connection takes the bytes. */
1010
+ scratch: u8[]
1011
+ /** The last accepted peer's address, in `nish:net`'s 18-byte form. */
1012
+ peer: u8[]
1013
+ /** The listening socket, which the program made with `tcpListen` and still owns. */
1014
+ listener: i32 = -1
1015
+
1016
+ /** A pool of `size` slots, at least one, serving `config` on `listener`. Every slot's buffers are allocated here. */
1017
+ constructor(config: Http1Config, listener: i32, size: i32) {
1018
+ this.config = config
1019
+ this.listener = listener
1020
+ this.connections = []
1021
+ 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
+ }
1027
+ this.scratch = new Array<u8>(config.chunkSize)
1028
+ this.peer = new Array<u8>(H1_ADDRESS_SIZE)
1029
+ }
1030
+
1031
+ /** How many slots the pool has. */
1032
+ size(): i32 {
1033
+ return toI32(this.fds.length)
1034
+ }
1035
+
1036
+ /** How many slots hold a connection. */
1037
+ busy(): i32 {
1038
+ let n: i32 = 0
1039
+ for (const fd of this.fds) {
1040
+ if (fd >= 0) {
1041
+ n = n + 1
1042
+ }
1043
+ }
1044
+ return n
1045
+ }
1046
+
1047
+ /** Whether `slot` is a slot of this pool that holds a connection. */
1048
+ holds(slot: i32): boolean {
1049
+ return slot >= 0 && slot < toI32(this.fds.length) && this.fds[slot] >= 0
1050
+ }
1051
+
1052
+ /** The descriptor of `slot`, for `pollAdd`; -1 for a free or unknown one. */
1053
+ fd(slot: i32): i32 {
1054
+ return this.holds(slot) ? this.fds[slot] : -1
1055
+ }
1056
+
1057
+ /** The connection of `slot`; a slot out of range names the first. */
1058
+ connection(slot: i32): Http1Connection {
1059
+ const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : H1_ZERO
1060
+ return this.connections[at]
1061
+ }
1062
+
1063
+ /**
1064
+ * 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.
1067
+ */
1068
+ accept(): i32 {
1069
+ const fd: i32 = tcpAccept(this.listener, this.peer)
1070
+ if (fd < 0) {
1071
+ return fd
1072
+ }
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
1084
+ }
1085
+
1086
+ /** The socket of `slot` is readable: reads it while the connection has room, then sends. Answers the interest. */
1087
+ readable(slot: i32): i32 {
1088
+ if (!this.holds(slot)) {
1089
+ return H1_DONE
1090
+ }
1091
+ const conn: Http1Connection = this.connections[slot]
1092
+ const fd: i32 = this.fds[slot]
1093
+ let moved: boolean = false
1094
+ while (true) {
1095
+ const room: i32 = conn.inputRoom()
1096
+ const want: i32 = room < toI32(this.scratch.length) ? room : toI32(this.scratch.length)
1097
+ if (want <= 0) {
1098
+ break
1099
+ }
1100
+ const n: i32 = netRead(fd, this.scratch, H1_ZERO, want)
1101
+ if (n <= 0) {
1102
+ if (n === 0) {
1103
+ conn.endInput()
1104
+ } else if (n !== H1_AGAIN) {
1105
+ conn.abort()
1106
+ }
1107
+ break
1108
+ }
1109
+ moved = true
1110
+ conn.feed(this.scratch, H1_ZERO, n)
1111
+ }
1112
+ if (moved) {
1113
+ conn.lastActive = monotonicNanos()
1114
+ }
1115
+ return this.flush(slot)
1116
+ }
1117
+
1118
+ /** The socket of `slot` is writable. Answers the interest. */
1119
+ writable(slot: i32): i32 {
1120
+ return this.flush(slot)
1121
+ }
1122
+
1123
+ /**
1124
+ * `slot`'s connection's next event; `H1_ERROR` for a free slot. While a
1125
+ * write is held back the output is sent first, so that the room it waits
1126
+ * for is made here and `H1_WRITE` comes in the same pass; otherwise what
1127
+ * the program writes in answer to one event and the next goes out together
1128
+ * at `flush`, as one segment rather than several.
1129
+ */
1130
+ next(slot: i32): i32 {
1131
+ if (!this.holds(slot)) {
1132
+ return H1_ERROR
1133
+ }
1134
+ const conn: Http1Connection = this.connections[slot]
1135
+ if (conn.writeHeld) {
1136
+ this.send(slot)
1137
+ }
1138
+ return conn.next()
1139
+ }
1140
+
1141
+ /**
1142
+ * Sends what `slot`'s connection holds until the socket would block, and
1143
+ * answers the interest: `H1_DONE` once the connection is over and sent,
1144
+ * otherwise read while it has room and write while it holds bytes.
1145
+ */
1146
+ flush(slot: i32): i32 {
1147
+ if (!this.holds(slot)) {
1148
+ return H1_DONE
1149
+ }
1150
+ const conn: Http1Connection = this.connections[slot]
1151
+ this.send(slot)
1152
+ if (conn.isDone()) {
1153
+ return H1_DONE
1154
+ }
1155
+ let wants: i32 = 0
1156
+ if (conn.inputRoom() > 0) {
1157
+ wants = wants | H1_WANT_READ
1158
+ }
1159
+ if (conn.wantsWrite()) {
1160
+ wants = wants | H1_WANT_WRITE
1161
+ }
1162
+ return wants
1163
+ }
1164
+
1165
+ /** Writes what `slot`'s connection holds, which it holds, until the socket would block. */
1166
+ send(slot: i32): void {
1167
+ const conn: Http1Connection = this.connections[slot]
1168
+ const fd: i32 = this.fds[slot]
1169
+ const pending: i32 = conn.outputEnd - conn.outputStart
1170
+ while (conn.wantsWrite()) {
1171
+ const n: i32 = netWrite(fd, conn.output, conn.outputStart, conn.outputEnd - conn.outputStart)
1172
+ if (n <= 0) {
1173
+ if (n !== H1_AGAIN) {
1174
+ conn.abort()
1175
+ }
1176
+ break
1177
+ }
1178
+ conn.consume(n)
1179
+ }
1180
+ if (conn.outputEnd - conn.outputStart < pending) {
1181
+ conn.lastActive = monotonicNanos()
1182
+ }
1183
+ }
1184
+
1185
+ /** Closes `slot`'s socket, whatever is left unsent, and frees the slot. */
1186
+ close(slot: i32): void {
1187
+ if (!this.holds(slot)) {
1188
+ return
1189
+ }
1190
+ netClose(this.fds[slot])
1191
+ this.fds[slot] = H1_FREE
1192
+ }
1193
+
1194
+ /** Closes every slot that has been idle past `idleTimeout` at `now` (`monotonicNanos`); answers how many. */
1195
+ expire(now: i64): i32 {
1196
+ let n: i32 = 0
1197
+ for (let k: i32 = 0; k < toI32(this.fds.length) && k < toI32(this.connections.length); k++) {
1198
+ if (this.fds[k] >= 0 && this.connections[k].idle(now)) {
1199
+ this.close(k)
1200
+ n = n + 1
1201
+ }
1202
+ }
1203
+ return n
1204
+ }
1205
+ }
1206
+
1207
+ /**
1208
+ * A pool of HTTP/1.1 connections over TLS 1.3 on one listening socket: one
1209
+ * `Http1Connection` beside each slot of a `TlsTcpServer`, run the way
1210
+ * `nish/net/http2-tls` runs HTTP/2. A handshake that chose ALPN `http/1.1`,
1211
+ * 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`.
1213
+ */
1214
+ export class Http1TlsServer {
1215
+ tls: TlsTcpServer
1216
+ connections: Http1Connection[]
1217
+ /** Where TLS decrypts into before the connection takes the bytes. */
1218
+ scratch: u8[]
1219
+
1220
+ /** A pool of `size` slots, at least one, serving `config` over TLS under `tlsConfig` on `listener`. */
1221
+ constructor(tlsConfig: TlsServerConfig, config: Http1Config, listener: i32, size: i32) {
1222
+ this.tls = new TlsTcpServer(tlsConfig, listener, size)
1223
+ this.connections = []
1224
+ for (let k: i32 = 0; k < this.tls.size(); k++) {
1225
+ this.connections.push(new Http1Connection(config))
1226
+ }
1227
+ this.scratch = new Array<u8>(config.chunkSize)
1228
+ }
1229
+
1230
+ /** How many slots the pool has. */
1231
+ size(): i32 {
1232
+ return this.tls.size()
1233
+ }
1234
+
1235
+ /** How many slots hold a connection. */
1236
+ busy(): i32 {
1237
+ return this.tls.busy()
1238
+ }
1239
+
1240
+ /** Whether `slot` holds a connection. */
1241
+ holds(slot: i32): boolean {
1242
+ return this.tls.holds(slot)
1243
+ }
1244
+
1245
+ /** The descriptor of `slot`, for `pollAdd`; -1 for a free one. */
1246
+ fd(slot: i32): i32 {
1247
+ return this.tls.fd(slot)
1248
+ }
1249
+
1250
+ /** The HTTP/1.1 connection of `slot`; a slot out of range names the first. */
1251
+ connection(slot: i32): Http1Connection {
1252
+ const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : H1_ZERO
1253
+ return this.connections[at]
1254
+ }
1255
+
1256
+ /** The ALPN protocol `slot`'s handshake chose, or empty. */
1257
+ alpn(slot: i32): string {
1258
+ return this.tls.connection(slot).tls.alpn
1259
+ }
1260
+
1261
+ /** Whether `slot`'s handshake is done and chose a protocol other than `http/1.1`. */
1262
+ refused(slot: i32): boolean {
1263
+ const chosen: string = this.alpn(slot)
1264
+ return this.tls.connection(slot).state === TLS_RECORD_STATE_OPEN && chosen !== "" && chosen !== H1_ALPN
1265
+ }
1266
+
1267
+ /**
1268
+ * Accepts the next waiting connection as `TlsTcpServer.accept` does, with
1269
+ * 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.
1271
+ */
1272
+ accept(serverRandom: u8[], ephemeralPrivate: u8[]): i32 {
1273
+ const slot: i32 = this.tls.accept(serverRandom, ephemeralPrivate)
1274
+ if (slot >= 0 && slot < toI32(this.connections.length)) {
1275
+ const conn: Http1Connection = this.connections[slot]
1276
+ conn.restart()
1277
+ conn.lastActive = monotonicNanos()
1278
+ }
1279
+ return slot
1280
+ }
1281
+
1282
+ /** The socket of `slot` is readable: TLS reads it, and what the connection has to send goes after. Answers the interest. */
1283
+ readable(slot: i32): i32 {
1284
+ if (this.holds(slot)) {
1285
+ this.connections[slot].lastActive = monotonicNanos()
1286
+ }
1287
+ this.tls.readable(slot)
1288
+ return this.flush(slot)
1289
+ }
1290
+
1291
+ /** The socket of `slot` is writable. Answers the interest. */
1292
+ writable(slot: i32): i32 {
1293
+ this.tls.writable(slot)
1294
+ return this.flush(slot)
1295
+ }
1296
+
1297
+ /** Signs `slot`'s CertificateVerify with `key`, as `TlsTcpServer.signP256` does. Answers the interest. */
1298
+ signP256(slot: i32, key: Secret<u8[]>): i32 {
1299
+ this.tls.signP256(slot, key)
1300
+ return this.flush(slot)
1301
+ }
1302
+
1303
+ /**
1304
+ * Moves what TLS has decrypted for `slot` into its connection, as far as
1305
+ * the connection has room, and answers the connection's next event. A free
1306
+ * slot, or one whose ALPN was another protocol, answers `H1_ERROR`.
1307
+ */
1308
+ next(slot: i32): i32 {
1309
+ if (!this.holds(slot) || this.refused(slot)) {
1310
+ return H1_ERROR
1311
+ }
1312
+ const conn: Http1Connection = this.connections[slot]
1313
+ while (true) {
1314
+ const room: i32 = conn.inputRoom()
1315
+ const want: i32 = room < toI32(this.scratch.length) ? room : toI32(this.scratch.length)
1316
+ if (want <= 0) {
1317
+ break
1318
+ }
1319
+ const n: i32 = this.tls.read(slot, this.scratch, H1_ZERO, want)
1320
+ if (n === 0) {
1321
+ conn.endInput()
1322
+ }
1323
+ if (n <= 0) {
1324
+ break
1325
+ }
1326
+ conn.feed(this.scratch, H1_ZERO, n)
1327
+ }
1328
+ // A held-back write finds its room here, as in `Http1Server.next`.
1329
+ if (conn.writeHeld) {
1330
+ this.send(slot)
1331
+ }
1332
+ return conn.next()
1333
+ }
1334
+
1335
+ /**
1336
+ * Writes what `slot`'s connection holds into TLS, as far as TLS takes it,
1337
+ * and sends it; shuts TLS down once the connection is over or ALPN chose
1338
+ * another protocol. Answers TLS's interest, with the write bit while the
1339
+ * connection still holds bytes.
1340
+ */
1341
+ flush(slot: i32): i32 {
1342
+ if (!this.holds(slot)) {
1343
+ return TLS_RECORD_DONE
1344
+ }
1345
+ if (this.refused(slot)) {
1346
+ return this.tls.shutdown(slot)
1347
+ }
1348
+ const conn: Http1Connection = this.connections[slot]
1349
+ this.send(slot)
1350
+ if (conn.isDone()) {
1351
+ return this.tls.shutdown(slot)
1352
+ }
1353
+ const wants: i32 = this.tls.interest(slot)
1354
+ return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
1355
+ }
1356
+
1357
+ /** Writes what `slot`'s connection holds into TLS, as far as TLS takes it. */
1358
+ send(slot: i32): void {
1359
+ const conn: Http1Connection = this.connections[slot]
1360
+ const pending: i32 = conn.outputEnd - conn.outputStart
1361
+ while (conn.wantsWrite()) {
1362
+ const n: i32 = this.tls.write(slot, conn.output, conn.outputStart, conn.outputEnd - conn.outputStart)
1363
+ if (n <= 0) {
1364
+ break
1365
+ }
1366
+ conn.consume(n)
1367
+ }
1368
+ if (conn.outputEnd - conn.outputStart < pending) {
1369
+ conn.lastActive = monotonicNanos()
1370
+ }
1371
+ }
1372
+
1373
+ /** Ends `slot`'s connection and frees the slot at once, as `TlsTcpServer.close` does. */
1374
+ close(slot: i32): void {
1375
+ this.tls.close(slot)
1376
+ }
1377
+
1378
+ /** Closes every slot that has been idle past `idleTimeout` at `now` (`monotonicNanos`); answers how many. */
1379
+ expire(now: i64): i32 {
1380
+ let n: i32 = 0
1381
+ for (let k: i32 = 0; k < this.tls.size() && k < toI32(this.connections.length); k++) {
1382
+ if (this.tls.holds(k) && this.connections[k].idle(now)) {
1383
+ this.tls.close(k)
1384
+ n = n + 1
1385
+ }
1386
+ }
1387
+ return n
1388
+ }
1389
+ }