@amritk/nish-x86_64-linux 0.20.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/INSTALL.md +52 -10
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2777 -0
  4. package/package.json +2 -1
  5. package/runtime/browser.d.ts +6 -0
  6. package/runtime/browser.mjs +77 -0
  7. package/runtime/core/arrays.d.ts +36 -0
  8. package/runtime/core/arrays.mjs +153 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +80 -0
  11. package/runtime/core/host.mjs +325 -0
  12. package/runtime/core/numbers.d.ts +66 -0
  13. package/runtime/core/numbers.mjs +343 -0
  14. package/runtime/core/result.d.ts +45 -0
  15. package/runtime/core/result.mjs +140 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +46 -2
  22. package/runtime/nish.h +75 -2
  23. package/runtime/nish.mjs +23 -3
  24. package/runtime/node.mjs +386 -0
  25. package/runtime/runtime-bytes.c +401 -0
  26. package/runtime/runtime-cross.c +1 -1
  27. package/runtime/runtime-host.c +1 -1
  28. package/runtime/runtime-net.c +12 -1
  29. package/runtime/runtime-os.c +1 -1
  30. package/runtime/runtime-parallel.c +1 -1
  31. package/runtime/runtime-simd.c +231 -7
  32. package/runtime/runtime-term.c +504 -0
  33. package/runtime/runtime-wasm.c +0 -11
  34. package/runtime/runtime.c +190 -6
  35. package/runtime/shim.mjs +6 -985
  36. package/runtime/sqlite/LICENSE.md +85 -0
  37. package/runtime/sqlite/README.md +45 -0
  38. package/runtime/sqlite/nish-sqlite.c +359 -0
  39. package/runtime/sqlite/sqlite3.c +271131 -0
  40. package/runtime/sqlite/sqlite3.h +14447 -0
  41. package/scripts/build.sh +311 -59
  42. package/std/LICENSE-fast-float +23 -0
  43. package/std/LICENSE-fdlibm +8 -0
  44. package/std/README.md +32 -12
  45. package/std/base64.ts +116 -0
  46. package/std/bytes.ts +256 -0
  47. package/std/cli.ts +344 -0
  48. package/std/collections.ts +6 -5
  49. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  50. package/std/crypto/base64url.ts +125 -0
  51. package/std/crypto/bcrypt.ts +1557 -0
  52. package/std/crypto/hmac.ts +138 -2
  53. package/std/crypto/x509.ts +11 -122
  54. package/std/json-tokenizer.ts +2998 -57
  55. package/std/json-value.ts +22 -3
  56. package/std/json.ts +87 -8
  57. package/std/math.ts +608 -0
  58. package/std/net/http1-server.ts +282 -40
  59. package/std/net/http1.ts +40 -21
  60. package/std/net/http3-server.ts +50 -12
  61. package/std/net/postgres.ts +2294 -0
  62. package/std/net/prefork.ts +23 -3
  63. package/std/net/quic-listener.ts +20 -4
  64. package/std/random.ts +66 -4
  65. package/std/regex.ts +4274 -0
  66. package/std/sqlite.ts +391 -0
  67. package/std/term-io.ts +978 -0
  68. package/std/term-pixels.ts +874 -0
  69. package/std/term-probe.ts +459 -0
  70. package/std/term-rate.ts +993 -0
  71. package/std/term.ts +2453 -0
  72. package/std/text.ts +90 -7
  73. package/std/yaml-lines.ts +141 -0
  74. package/std/yaml.ts +4675 -0
@@ -0,0 +1,2294 @@
1
+ /**
2
+ * `nish/net/postgres` — the client side of PostgreSQL's frontend/backend
3
+ * protocol, version 3.0: a sans-IO connection that writes the frontend's
4
+ * messages into an output buffer and reads the backend's from an input
5
+ * buffer, for a program that owns its socket (`nish:net`) and its loop.
6
+ *
7
+ * const config = new PgConfig();
8
+ * config.user = "app";
9
+ * config.database = "app";
10
+ * const nonce = new Array<u8>(18);
11
+ * crypto.getRandomValues(nonce);
12
+ * const conn = new PgConnection(config, password, nonce);
13
+ * // write conn.outputBuffer()[conn.outputStart() .. conn.outputEnd()), then conn.sent(n)
14
+ * // read into conn.inputBuffer() at conn.inputEnd(), up to conn.inputRoom() bytes, then conn.received(n)
15
+ * let event: i32 = conn.next();
16
+ * while (event !== PG_NEED_MORE && event !== PG_FAILED) {
17
+ * // PG_READY: authenticated; prepare statements and send queries
18
+ * // conn.prepare("by_id", "select id, name from users where id = $1", [23]); conn.sync()
19
+ * // conn.bindStart("by_id"); conn.paramInt(7); conn.bindEnd(); conn.sync()
20
+ * // PG_ROW: conn.int(0), conn.colStart(1) .. + conn.colLength(1) in conn.inputBuffer()
21
+ * // PG_COMPLETE: conn.tagRows(); PG_ERROR: conn.lastError.code
22
+ * // PG_GROUP_DONE: one sync()'s group has been answered
23
+ * event = conn.next();
24
+ * }
25
+ *
26
+ * **Events.** `next()` answers `PG_READY` once, when the server has
27
+ * authenticated the connection and is ready for queries; `PG_DESCRIBED` when a
28
+ * prepared statement has been described (`columns` and `columnType(c)` say
29
+ * what its rows hold, `columns` is 0 when it returns none); `PG_ROW` for each
30
+ * row, its columns as windows into the input buffer (`colStart(c)`,
31
+ * `colLength(c)`, -1 for NULL); `PG_COMPLETE` when an execution has finished
32
+ * (`tagIs("UPDATE")`, `tagRows()`); `PG_ERROR` when a statement in the current
33
+ * group failed (`lastError`); `PG_NOTICE` for a notice (`lastNotice`);
34
+ * `PG_GROUP_DONE` when a group has been answered to its end (`groupFailed`,
35
+ * `txStatus`); and `PG_FAILED`, final: the connection broke a rule or the
36
+ * server ended it, `failure` says which and `lastError` holds the server's
37
+ * reason when it gave one. `PG_NEED_MORE` asks for more input.
38
+ *
39
+ * **Windows and buffers.** A column's window, the tag `tagIs` and `tagRows`
40
+ * read are valid until the next `next()`,
41
+ * which may move the unread input to the start of the buffer or replace the
42
+ * buffer with a larger one. Fetch `inputBuffer()` again after every `next()`
43
+ * rather than keep it; the same holds for `outputBuffer()` after a builder
44
+ * call.
45
+ *
46
+ * **Groups and pipelining.** Every statement is sent with the extended query
47
+ * protocol: `prepare` (Parse and Describe), `bindStart`, the `param...` calls
48
+ * and `bindEnd` (Bind and Execute), `closeStatement` (Close), then `sync()`,
49
+ * which ends the group. The server answers groups in order and skips the rest
50
+ * of a group after an error, so one failed statement costs only its own group.
51
+ * In a failed group, `PG_DESCRIBED` comes once for each `prepare` the server
52
+ * parsed before the error: a program that counts them knows which statements
53
+ * exist (named ones last past the group) and which must be prepared again.
54
+ * Up to `maxInFlight` groups may wait for their answers at once; `canSend()`
55
+ * says whether another may start, and the builder calls refuse (and write
56
+ * nothing) when it may not. Nothing waits for a round trip that the protocol
57
+ * does not need: a program prepares a statement and executes it in the same
58
+ * flight.
59
+ *
60
+ * **Text results, no copies.** Every parameter and every result column is in
61
+ * text format. A row is read where it lies in the input buffer: `next()` frames
62
+ * each message once, validates its layout, and records each column's start and
63
+ * length in two reused arrays, so a row costs no allocation. `int`, `bool`,
64
+ * `isNull` and `PgText.timestamp` decode a column in place; `PgArrayWalker`
65
+ * walks a one-dimensional `text[]` in place. `columnText(c)` is the one that
66
+ * allocates, a string.
67
+ *
68
+ * **Authentication.** SCRAM-SHA-256 (RFC 5802, RFC 7677) or none (`trust`).
69
+ * The password arrives as a `Secret<u8[]>`; the connection keeps a copy in a
70
+ * holder that is wiped as soon as the proof is computed, or when the
71
+ * connection fails or is disposed. The server's signature is checked in
72
+ * constant time before `AuthenticationOk` is believed, and an
73
+ * `AuthenticationOk` that skips the end of an exchange SCRAM began is refused.
74
+ * Cleartext and MD5 passwords, and SASL mechanisms other than SCRAM-SHA-256,
75
+ * are refused: with no TLS here the first sends the password as it is, and the
76
+ * second is a hash any eavesdropper can replay. The password's bytes are used
77
+ * as they are, without SASLprep (RFC 4013): an ASCII password works, and a
78
+ * non-ASCII one works only when it is already in the form SASLprep would give
79
+ * (PostgreSQL itself falls back to the raw bytes when it cannot prepare one).
80
+ * `docs/security/postgres.md` is the record.
81
+ *
82
+ * **Caps.** `PgConfig` bounds what a server can make the client hold: the
83
+ * longest message (`maxMessage`), the widest row (`maxColumns`) and the SCRAM
84
+ * iteration count (`minIterations`, `maxIterations`); and what a program can
85
+ * queue unsent (`maxOutput`), past which a builder call answers false. A
86
+ * message longer than the cap, a length below 4, a layout that does not add
87
+ * up, a message the protocol does not allow at that point (COPY among them),
88
+ * an encoding other than UTF-8, and a `DateStyle` other than ISO or a
89
+ * `TimeZone` other than UTC (at startup or after a `SET`), each fail the
90
+ * connection with their own `failure` code. The constructor panics on a cap
91
+ * out of range, as on a missing user or a short nonce.
92
+ *
93
+ * **Pools.** `PgPool` holds up to `size` connections to one server and a FIFO
94
+ * queue of up to `queueSize` requests, still sans-IO: `wanted()` names a slot
95
+ * that needs a socket, `connect(slot, nonce)` starts its connection, and the
96
+ * program moves its bytes as above through `connection(slot)`. A request is a
97
+ * ticket from `request()`; `dispatch()` gives the oldest to the ready
98
+ * connection with the fewest groups in flight, the program builds its group and
99
+ * ends it with `send()`, and `next(slot)` reads that connection's events with
100
+ * `ticket` naming the request. Statements given to `register` are prepared on
101
+ * each connection ahead of its first request. A connection that fails reports
102
+ * `PG_LOST` for each request it took, then `PG_FAILED`; the program closes the
103
+ * socket and the slot is built again on its next use. `tests/postgres/carrier.ts`
104
+ * is a whole carrier: one `pollWait` loop holding the pool's sockets beside an
105
+ * HTTP listener.
106
+ *
107
+ * Written from PostgreSQL's "Frontend/Backend Protocol" chapter (version 18)
108
+ * and RFC 5802 and RFC 7677; not ported from another implementation.
109
+ */
110
+ import { copyBytes, getI32BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
111
+ import { base64StdPadDecode, base64StdPadEncode } from "nish/crypto/base64url"
112
+ import { timingSafeEqual } from "nish/crypto/ct"
113
+ import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
114
+ import { Sha256 } from "nish/crypto/sha256"
115
+ import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
116
+
117
+ /** `next()` needs more input before it can say anything. */
118
+ export const PG_NEED_MORE: i32 = 0
119
+ /** The connection is authenticated and ready for queries; answered once. */
120
+ export const PG_READY: i32 = 1
121
+ /** A row: `columns` columns, each at `colStart(c)` for `colLength(c)` bytes. */
122
+ export const PG_ROW: i32 = 2
123
+ /** An execution has finished: `tagIs`, `tagRows`. */
124
+ export const PG_COMPLETE: i32 = 3
125
+ /** A statement in the current group failed: `lastError`. */
126
+ export const PG_ERROR: i32 = 4
127
+ /** A group has been answered: `groupFailed`, `txStatus`. */
128
+ export const PG_GROUP_DONE: i32 = 5
129
+ /** A notice: `lastNotice`. */
130
+ export const PG_NOTICE: i32 = 6
131
+ /** A prepared statement has been described: `columns`, `columnType(c)`. */
132
+ export const PG_DESCRIBED: i32 = 7
133
+ /** Final: the connection is over, and `failure` says why. */
134
+ export const PG_FAILED: i32 = 8
135
+
136
+ /** `failure` while the connection is alive. */
137
+ export const PG_ERR_NONE: i32 = 0
138
+ /** A message length below 4, or a message whose layout does not add up. */
139
+ export const PG_ERR_FRAME: i32 = 1
140
+ /** A message longer than `maxMessage`, or a row wider than `maxColumns`. */
141
+ export const PG_ERR_TOO_BIG: i32 = 2
142
+ /** A message the protocol does not allow at this point. */
143
+ export const PG_ERR_UNEXPECTED: i32 = 3
144
+ /** An authentication method this client refuses (cleartext, MD5, GSS, SSPI, a SASL mechanism other than SCRAM-SHA-256). */
145
+ export const PG_ERR_AUTH_UNSUPPORTED: i32 = 4
146
+ /** A SCRAM exchange the server got wrong: a malformed message, a nonce that does not extend ours, an iteration count out of bounds. */
147
+ export const PG_ERR_SCRAM: i32 = 5
148
+ /** The server's SCRAM signature is not the one the password gives: it does not know the password. */
149
+ export const PG_ERR_SERVER_SIGNATURE: i32 = 6
150
+ /** The server sent a FATAL or PANIC error, or any error before the connection was ready: `lastError`. */
151
+ export const PG_ERR_SERVER: i32 = 7
152
+ /** The server's `client_encoding` is not UTF8. */
153
+ export const PG_ERR_ENCODING: i32 = 8
154
+ /** The program reported the end of the stream (`closed()`). */
155
+ export const PG_ERR_CLOSED: i32 = 9
156
+ /** The server's `DateStyle` is not ISO or its `TimeZone` is not UTC, the two the decoders read: at startup, or after a `SET`. */
157
+ export const PG_ERR_SETTING: i32 = 10
158
+
159
+ /** `PgArrayWalker.next()`: an element, at `start` for `length` bytes. */
160
+ export const PG_ARRAY_ELEMENT: i32 = 0
161
+ /** `PgArrayWalker.next()`: a NULL element. */
162
+ export const PG_ARRAY_NULL: i32 = -1
163
+ /** `PgArrayWalker.next()`: no more elements. */
164
+ export const PG_ARRAY_END: i32 = -2
165
+ /** `PgArrayWalker.next()`: the text is not a one-dimensional array literal. */
166
+ export const PG_ARRAY_BAD: i32 = -3
167
+
168
+ // The connection's states, in the order they come.
169
+ const PG_STATE_AUTH: i32 = 0
170
+ const PG_STATE_SCRAM_FIRST: i32 = 1
171
+ const PG_STATE_SCRAM_FINAL: i32 = 2
172
+ const PG_STATE_STARTING: i32 = 3
173
+ const PG_STATE_READY: i32 = 4
174
+ const PG_STATE_FAILED: i32 = 5
175
+
176
+ // The frontend's message types, and the bytes the builder writes.
177
+ const PG_FE_PASSWORD: i32 = 112 // 'p', every SASL response
178
+ const PG_FE_PARSE: i32 = 80 // 'P'
179
+ const PG_FE_DESCRIBE: i32 = 68 // 'D'
180
+ const PG_FE_BIND: i32 = 66 // 'B'
181
+ const PG_FE_EXECUTE: i32 = 69 // 'E'
182
+ const PG_FE_CLOSE: i32 = 67 // 'C'
183
+ const PG_FE_SYNC: i32 = 83 // 'S'
184
+ const PG_FE_TERMINATE: i32 = 88 // 'X'
185
+ const PG_TARGET_STATEMENT: i32 = 83 // 'S', what Describe and Close name
186
+ const PG_ZERO: i32 = 0
187
+ const PG_COMMA: i32 = 44
188
+ const PG_QUOTE: i32 = 34
189
+ const PG_BACKSLASH: i32 = 92
190
+ const PG_LBRACE: i32 = 123
191
+ const PG_RBRACE: i32 = 125
192
+
193
+ // The protocol version the startup message asks for: 3.0.
194
+ const PG_PROTOCOL: i32 = 196608
195
+ // The bytes of the client nonce, which base64 spells in 24 characters.
196
+ const PG_NONCE_BYTES: i32 = 18
197
+ // What a bind's end (Bind's result formats, then Execute: 2 + 10) and a Sync (5)
198
+ // take: kept free under `maxOutput` by every other builder call, so that
199
+ // `bindEnd()` and `sync()` are never refused for room.
200
+ const PG_TAIL: i32 = 17
201
+ // The largest `maxMessage` and `maxOutput`. The input buffer doubles up to one
202
+ // past them; the output buffer doubles only while twice its size fits in an i32.
203
+ const PG_MAX_CAP: i32 = 1073741823
204
+ // The largest `maxIterations`: the count is read digit by digit, times ten, in an i32.
205
+ const PG_MAX_ITERATIONS: i32 = 214748363
206
+
207
+ /** The caps a connection enforces, and the startup parameters it sends. */
208
+ export class PgConfig {
209
+ /** The role to connect as; required. */
210
+ user: string = ""
211
+ /** The database; empty means the server's default, the role's name. */
212
+ database: string = ""
213
+ /** `application_name`, shown in `pg_stat_activity`; empty sends none. */
214
+ applicationName: string = ""
215
+ /** The startup `options` parameter (`-c name=value ...`); empty sends none. */
216
+ options: string = ""
217
+ /** The longest backend message, in bytes, its type byte aside; past it, `PG_ERR_TOO_BIG`. */
218
+ maxMessage: i32 = 1048576
219
+ /** The most groups waiting for their answers at once. */
220
+ maxInFlight: i32 = 16
221
+ /** The input buffer's first size, in bytes; it grows to a message's length, up to `maxMessage` + 1. */
222
+ inputSize: i32 = 8192
223
+ /** The widest row; past it, `PG_ERR_TOO_BIG`. PostgreSQL's own limit is 1664. */
224
+ maxColumns: i32 = 1664
225
+ /** The fewest SCRAM iterations accepted; 4096 is PostgreSQL's default and RFC 7677's floor. */
226
+ minIterations: i32 = 4096
227
+ /** The most SCRAM iterations computed, so a server cannot make the client spin. */
228
+ maxIterations: i32 = 100000
229
+ /**
230
+ * The most bytes the builder calls may queue unsent; a call that would pass
231
+ * it answers false and writes nothing, so a program that never drains its
232
+ * output cannot grow the buffer without bound.
233
+ */
234
+ maxOutput: i32 = 1048576
235
+ }
236
+
237
+ /** A server's ErrorResponse or NoticeResponse: the fields a program acts on. */
238
+ export class PgError {
239
+ /** ERROR, FATAL or PANIC; WARNING, NOTICE, DEBUG, INFO or LOG for a notice. Never localised. */
240
+ readonly severity: string
241
+ /** The SQLSTATE, five characters: `23505` is a unique violation. */
242
+ readonly code: string
243
+ /** The primary message. */
244
+ readonly message: string
245
+ /** The detail, or "". */
246
+ readonly detail: string
247
+ /** The hint, or "". */
248
+ readonly hint: string
249
+ /** The constraint that was violated, or "". */
250
+ readonly constraint: string
251
+ /** The 1-based character position in the statement's text, or "". */
252
+ readonly position: string
253
+
254
+ constructor(
255
+ severity: string,
256
+ code: string,
257
+ message: string,
258
+ detail: string,
259
+ hint: string,
260
+ constraint: string,
261
+ position: string
262
+ ) {
263
+ this.severity = severity
264
+ this.code = code
265
+ this.message = message
266
+ this.detail = detail
267
+ this.hint = hint
268
+ this.constraint = constraint
269
+ this.position = position
270
+ }
271
+ }
272
+
273
+ /** A fresh copy of `bytes`; the password holder keeps one. */
274
+ const pgCopyBytes = (bytes: u8[]): u8[] => {
275
+ const copy: u8[] = new Array<u8>(toI32(bytes.length))
276
+ copy.set(bytes, 0)
277
+ return copy
278
+ }
279
+
280
+ /** Whether every byte of `bytes` is zero. */
281
+ const pgAllZero = (bytes: u8[]): boolean => {
282
+ for (const b of bytes) {
283
+ if (toI32(b) !== 0) {
284
+ return false
285
+ }
286
+ }
287
+ return true
288
+ }
289
+
290
+ /**
291
+ * The connection's copy of the password, wiped once the proof is computed.
292
+ * The holder outlives the wipe, so that `wiped()` reads the very bytes.
293
+ */
294
+ class PgPassword {
295
+ readonly bytes: Secret<u8[]>
296
+ /** Whether `wipe()` has run; the bytes are no longer a password. */
297
+ gone: boolean = false
298
+
299
+ constructor(password: Secret<u8[]>) {
300
+ this.bytes = secret(expose(password, pgCopyBytes))
301
+ }
302
+
303
+ wipe(): void {
304
+ wipe(this.bytes)
305
+ this.gone = true
306
+ }
307
+
308
+ wiped(): boolean {
309
+ return expose(this.bytes, pgAllZero)
310
+ }
311
+ }
312
+
313
+ /** Panics on a `PgConfig` that is a mistake in the program: no user, a cap out of range, a NUL in a startup parameter. */
314
+ const pgCheckConfig = (config: PgConfig): void => {
315
+ if (config.user.length === 0) {
316
+ panic("nish/net/postgres: PgConfig.user is required")
317
+ }
318
+ if (
319
+ config.maxMessage < 1024 ||
320
+ config.maxMessage > PG_MAX_CAP ||
321
+ config.maxOutput < 1024 ||
322
+ config.maxOutput > PG_MAX_CAP ||
323
+ config.maxInFlight < 1 ||
324
+ config.maxColumns < 1 ||
325
+ config.maxColumns > 65535 ||
326
+ config.minIterations < 1 ||
327
+ config.maxIterations < config.minIterations ||
328
+ config.maxIterations > PG_MAX_ITERATIONS
329
+ ) {
330
+ panic("nish/net/postgres: a PgConfig cap out of range")
331
+ }
332
+ if (
333
+ pgHasNul(config.user) ||
334
+ pgHasNul(config.database) ||
335
+ pgHasNul(config.applicationName) ||
336
+ pgHasNul(config.options)
337
+ ) {
338
+ panic("nish/net/postgres: a startup parameter holds a NUL byte")
339
+ }
340
+ }
341
+
342
+ /** The bytes of `text`. */
343
+ const pgBytesOf = (text: string): u8[] => {
344
+ const out: u8[] = []
345
+ const n: i32 = toI32(text.length)
346
+ for (let i: i32 = 0; i < n; i++) {
347
+ out.push(toU8(text.charCodeAt(i)))
348
+ }
349
+ return out
350
+ }
351
+
352
+ /** Whether `text` holds a NUL byte, which a protocol string cannot. */
353
+ const pgHasNul = (text: string): boolean => {
354
+ const n: i32 = toI32(text.length)
355
+ for (let i: i32 = 0; i < n; i++) {
356
+ if (text.charCodeAt(i) === 0) {
357
+ return true
358
+ }
359
+ }
360
+ return false
361
+ }
362
+
363
+ /** The first `byte` in `b[from, end)`, or `end`. */
364
+ const pgFind = (b: u8[], from: i32, end: i32, byte: i32): i32 => {
365
+ if (from < 0 || end > toI32(b.length)) {
366
+ panic("nish/net/postgres: pgFind outside the buffer")
367
+ }
368
+ for (let i: i32 = from; i < end; i++) {
369
+ if (toI32(b[i]) === byte) {
370
+ return i
371
+ }
372
+ }
373
+ return end
374
+ }
375
+
376
+ /** Whether `b[start, start + length)` spells `text`. */
377
+ const pgBytesAre = (b: u8[], start: i32, length: i32, text: string): boolean => {
378
+ const n: i32 = toI32(text.length)
379
+ if (length !== n || start < 0 || start > toI32(b.length) - n) {
380
+ return false
381
+ }
382
+ for (let i: i32 = 0; i < n; i++) {
383
+ if (toI32(b[start + i]) !== toI32(text.charCodeAt(i))) {
384
+ return false
385
+ }
386
+ }
387
+ return true
388
+ }
389
+
390
+ /**
391
+ * The salted password, PBKDF2-HMAC-SHA-256 over the password and the salt
392
+ * (RFC 5802 §3, `Hi`), 32 bytes, or `null` when it cannot be computed.
393
+ *
394
+ * The caller has bounded `iterations` by `maxIterations`, since the time is
395
+ * linear in it.
396
+ */
397
+ const pgSaltedPassword = (password: Secret<u8[]>, salt: u8[], iterations: i32): Secret<u8[]> | null =>
398
+ pbkdf2HmacSha256(password, salt, iterations, 32)
399
+
400
+ /**
401
+ * SHA-256 of `data` through a hasher of its own, whose state, block and
402
+ * schedule are wiped before it returns: the one-shot `sha256` leaves the last
403
+ * block of its input in a hasher nothing wipes, and here the input is
404
+ * ClientKey.
405
+ */
406
+ const pgSha256Wiped = (data: u8[]): u8[] => {
407
+ const h = new Sha256()
408
+ h.update(data, toI32(0), toI32(data.length))
409
+ const digest: u8[] = h.digest()
410
+ wipe(h.state)
411
+ wipe(h.block)
412
+ wipe(h.schedule)
413
+ h.fill = 0
414
+ h.total = 0
415
+ h.finished = true
416
+ return digest
417
+ }
418
+
419
+ /**
420
+ * RFC 5802 §3 from the salted password and the AuthMessage: the ClientProof
421
+ * (32 bytes) followed by the ServerSignature (32 bytes). Every key in between
422
+ * is wiped before it returns.
423
+ */
424
+ const pgScramKeys = (salted: u8[], authMessage: u8[]): u8[] => {
425
+ const clientKey: u8[] = hmacSha256(salted, pgBytesOf("Client Key"))
426
+ const storedKey: u8[] = pgSha256Wiped(clientKey)
427
+ const clientSignature: u8[] = hmacSha256(storedKey, authMessage)
428
+ const serverKey: u8[] = hmacSha256(salted, pgBytesOf("Server Key"))
429
+ const serverSignature: u8[] = hmacSha256(serverKey, authMessage)
430
+ const out: u8[] = new Array<u8>(64)
431
+ if (clientKey.length < 32 || clientSignature.length < 32 || serverSignature.length < 32) {
432
+ panic("nish/net/postgres: an HMAC-SHA-256 shorter than 32 bytes")
433
+ }
434
+ for (let i: i32 = 0; i < 32; i++) {
435
+ out[i] = clientKey[i] ^ clientSignature[i]
436
+ out[32 + i] = serverSignature[i]
437
+ }
438
+ wipe(clientKey)
439
+ wipe(storedKey)
440
+ wipe(clientSignature)
441
+ wipe(serverKey)
442
+ return out
443
+ }
444
+
445
+ /** Decoders for PostgreSQL's text format, in place; a bad input sets `failed`, which stays set. */
446
+ export class PgText {
447
+ /** Whether a decode since the last `reset()` was given something it could not read. */
448
+ failed: boolean = false
449
+
450
+ /** Clears `failed`. */
451
+ reset(): void {
452
+ this.failed = false
453
+ }
454
+
455
+ /** An `int2`, `int4` or `int8` column: an optional `-` and 1 to 19 digits; 0 and `failed` otherwise, and on overflow. */
456
+ int(b: u8[], start: i32, length: i32): i64 {
457
+ const end: i32 = start + length
458
+ if (length <= 0 || start < 0 || end < start || end > toI32(b.length)) {
459
+ this.failed = true
460
+ return 0
461
+ }
462
+ const negative: boolean = toI32(b[start]) === 45
463
+ let i: i32 = start
464
+ if (negative) {
465
+ i++
466
+ }
467
+ if (i === end) {
468
+ this.failed = true
469
+ return 0
470
+ }
471
+ // Accumulated as a negative number, so that the most negative i64 fits.
472
+ const lastDigit: i64 = negative ? 8 : 7
473
+ // The most negative i64 divided by ten, which a literal cannot spell exactly.
474
+ const minTenth: i64 = toI64(-922337203) * 1000000000 - 685477580
475
+ let acc: i64 = 0
476
+ while (i < end) {
477
+ const d: i32 = toI32(b[i]) - 48
478
+ if (d < 0 || d > 9) {
479
+ this.failed = true
480
+ return 0
481
+ }
482
+ const digit: i64 = toI64(d)
483
+ if (acc < minTenth || (acc === minTenth && digit > lastDigit)) {
484
+ this.failed = true
485
+ return 0
486
+ }
487
+ acc = acc * 10 - digit
488
+ i++
489
+ }
490
+ return negative ? acc : -acc
491
+ }
492
+
493
+ /** A `bool` column: `t` or `f`; false and `failed` otherwise. */
494
+ bool(b: u8[], start: i32, length: i32): boolean {
495
+ if (length === 1 && start >= 0 && start < toI32(b.length)) {
496
+ const c: i32 = toI32(b[start])
497
+ if (c === 116) {
498
+ return true
499
+ }
500
+ if (c === 102) {
501
+ return false
502
+ }
503
+ }
504
+ this.failed = true
505
+ return false
506
+ }
507
+
508
+ /**
509
+ * A `timestamptz` column, as the connection's `DateStyle=ISO` and
510
+ * `TimeZone=UTC` spell it (`2024-01-02 03:04:05.123456+00`), or a
511
+ * `timestamp` column (the same without `+00`), written to `out[at, at + 24)`
512
+ * as `2024-01-02T03:04:05.123Z`, the fraction cut to milliseconds. Answers
513
+ * 24, or -1 and `failed` for anything else: a year outside 0001–9999, BC,
514
+ * `infinity`, an offset other than `+00`.
515
+ */
516
+ timestamp(b: u8[], start: i32, length: i32, out: u8[], at: i32): i32 {
517
+ if (
518
+ start < 0 ||
519
+ length < 19 ||
520
+ start > toI32(b.length) - length ||
521
+ at < 0 ||
522
+ at + 24 > toI32(out.length)
523
+ ) {
524
+ this.failed = true
525
+ return -1
526
+ }
527
+ // The fixed part: YYYY-MM-DD HH:MM:SS. Its five separators are where they
528
+ // belong and are not digits, so the other fourteen bytes are digits
529
+ // exactly when the part holds five non-digits.
530
+ let p: i32 = start + 19
531
+ const end: i32 = start + length
532
+ if (p < 0 || end > toI32(b.length)) {
533
+ this.failed = true
534
+ return -1
535
+ }
536
+ let others: i32 = 0
537
+ for (let i: i32 = start; i < p && i < toI32(b.length); i++) {
538
+ const c: i32 = toI32(b[i])
539
+ if (c < 48 || c > 57) {
540
+ others++
541
+ }
542
+ }
543
+ if (
544
+ others !== 5 ||
545
+ toI32(b[start + 4]) !== 45 ||
546
+ toI32(b[start + 7]) !== 45 ||
547
+ toI32(b[start + 10]) !== 32 ||
548
+ toI32(b[start + 13]) !== 58 ||
549
+ toI32(b[start + 16]) !== 58
550
+ ) {
551
+ this.failed = true
552
+ return -1
553
+ }
554
+ let millis: i32 = 0
555
+ if (p < end && toI32(b[p]) === 46) {
556
+ p++
557
+ let digits: i32 = 0
558
+ while (p < end && toI32(b[p]) >= 48 && toI32(b[p]) <= 57) {
559
+ if (digits < 3) {
560
+ millis = millis * 10 + toI32(b[p]) - 48
561
+ }
562
+ digits++
563
+ p++
564
+ }
565
+ if (digits < 1 || digits > 6) {
566
+ this.failed = true
567
+ return -1
568
+ }
569
+ while (digits < 3) {
570
+ millis = millis * 10
571
+ digits++
572
+ }
573
+ }
574
+ if (p + 3 === end && toI32(b[p]) === 43 && toI32(b[p + 1]) === 48 && toI32(b[p + 2]) === 48) {
575
+ p = end
576
+ }
577
+ // The year's four bytes are digits, so their OR is '0' exactly when the year is 0000.
578
+ const year: i32 = toI32(b[start]) | toI32(b[start + 1]) | toI32(b[start + 2]) | toI32(b[start + 3])
579
+ if (p !== end || year === 48) {
580
+ this.failed = true
581
+ return -1
582
+ }
583
+ copyBytes(out, at, b, start, 19)
584
+ out[at + 10] = toU8(84)
585
+ out[at + 19] = toU8(46)
586
+ out[at + 20] = toU8(millis / 100 + 48)
587
+ out[at + 21] = toU8(((millis / 10) % 10) + 48)
588
+ out[at + 22] = toU8((millis % 10) + 48)
589
+ out[at + 23] = toU8(90)
590
+ return 24
591
+ }
592
+ }
593
+
594
+ /**
595
+ * A walk over a one-dimensional `text[]` column in place: `start` the
596
+ * column, then `next()` until `PG_ARRAY_END` or `PG_ARRAY_BAD`. Each element
597
+ * is the window `[start, start + length)` of the column's buffer; a quoted
598
+ * element with a backslash escape in it has `escaped` set, and `copy` writes
599
+ * its bytes unescaped. An unquoted `NULL`, in any case, is `PG_ARRAY_NULL`.
600
+ * Dimension decorations (`[1:2]={...}`) and nested arrays are `PG_ARRAY_BAD`.
601
+ */
602
+ export class PgArrayWalker {
603
+ /** The current element's first byte in the buffer. */
604
+ start: i32 = 0
605
+ /** The current element's length, escapes included. */
606
+ length: i32 = 0
607
+ /** Whether the current element holds a backslash escape. */
608
+ escaped: boolean = false
609
+ private pos: i32 = 0
610
+ private end: i32 = 0
611
+ private first: boolean = true
612
+ private bad: boolean = false
613
+ private b: u8[]
614
+
615
+ constructor() {
616
+ this.b = []
617
+ }
618
+
619
+ /** Starts a walk over `b[start, start + length)`. */
620
+ begin(b: u8[], start: i32, length: i32): void {
621
+ this.b = b
622
+ this.first = true
623
+ this.bad = !(start >= 0 && length >= 2 && start + length <= toI32(b.length))
624
+ if (!this.bad) {
625
+ this.bad = toI32(b[start]) !== 123 || toI32(b[start + length - 1]) !== 125
626
+ }
627
+ this.pos = start + 1
628
+ this.end = start + length - 1
629
+ }
630
+
631
+ private refuse(): i32 {
632
+ this.bad = true
633
+ return PG_ARRAY_BAD
634
+ }
635
+
636
+ /** The next element: `PG_ARRAY_ELEMENT`, `PG_ARRAY_NULL`, `PG_ARRAY_END` or `PG_ARRAY_BAD`. */
637
+ next(): i32 {
638
+ if (this.bad) {
639
+ return PG_ARRAY_BAD
640
+ }
641
+ const b: u8[] = this.b
642
+ const end: i32 = this.end
643
+ if (this.pos < 0 || end > toI32(b.length)) {
644
+ return this.refuse()
645
+ }
646
+ if (this.first) {
647
+ this.first = false
648
+ if (this.pos === end) {
649
+ return PG_ARRAY_END
650
+ }
651
+ } else {
652
+ if (this.pos === end) {
653
+ return PG_ARRAY_END
654
+ }
655
+ if (toI32(b[this.pos]) !== 44) {
656
+ return this.refuse()
657
+ }
658
+ this.pos += 1
659
+ }
660
+ let p: i32 = this.pos
661
+ if (p < 0) {
662
+ return this.refuse()
663
+ }
664
+ this.escaped = false
665
+ if (p < end && toI32(b[p]) === 34) {
666
+ p++
667
+ this.start = p
668
+ while (p < end && toI32(b[p]) !== 34) {
669
+ if (toI32(b[p]) === 92) {
670
+ this.escaped = true
671
+ p++
672
+ }
673
+ p++
674
+ }
675
+ if (p >= end) {
676
+ return this.refuse()
677
+ }
678
+ this.length = p - this.start
679
+ this.pos = p + 1
680
+ if (this.pos !== end && toI32(b[this.pos]) !== 44) {
681
+ return this.refuse()
682
+ }
683
+ return PG_ARRAY_ELEMENT
684
+ }
685
+ this.start = p
686
+ while (p < end && toI32(b[p]) !== 44) {
687
+ const c: i32 = toI32(b[p])
688
+ if (c === 123 || c === 125 || c === 34 || c === 92 || c === 32) {
689
+ return this.refuse()
690
+ }
691
+ p++
692
+ }
693
+ this.length = p - this.start
694
+ this.pos = p
695
+ if (this.length === 0) {
696
+ return this.refuse()
697
+ }
698
+ if (this.length === 4 && p >= 4) {
699
+ const n: i32 = toI32(b[p - 4]) | 32
700
+ const u: i32 = toI32(b[p - 3]) | 32
701
+ const l1: i32 = toI32(b[p - 2]) | 32
702
+ const l2: i32 = toI32(b[p - 1]) | 32
703
+ if (n === 110 && u === 117 && l1 === 108 && l2 === 108) {
704
+ return PG_ARRAY_NULL
705
+ }
706
+ }
707
+ return PG_ARRAY_ELEMENT
708
+ }
709
+
710
+ /** Writes the current element, unescaped, to `out[at, ...)`; answers its length, or -1 when it does not fit. */
711
+ copy(out: u8[], at: i32): i32 {
712
+ const b: u8[] = this.b
713
+ let n: i32 = 0
714
+ let p: i32 = this.start
715
+ const end: i32 = this.start + this.length
716
+ if (p < 0 || end > toI32(b.length)) {
717
+ return -1
718
+ }
719
+ while (p < end) {
720
+ if (toI32(b[p]) === 92) {
721
+ p++
722
+ }
723
+ if (p >= end || at + n >= toI32(out.length) || at < 0) {
724
+ return -1
725
+ }
726
+ out[at + n] = b[p]
727
+ n++
728
+ p++
729
+ }
730
+ return n
731
+ }
732
+ }
733
+
734
+ /**
735
+ * One connection to a PostgreSQL server, sans IO: the program moves bytes
736
+ * between the socket and the two buffers, and `next()` says what they meant.
737
+ */
738
+ export class PgConnection {
739
+ /** Why the connection failed, `PG_ERR_NONE` while it has not. */
740
+ failure: i32 = 0
741
+ /** The columns of the current row, or of the statement just described. */
742
+ columns: i32 = 0
743
+ /** The transaction status the last `PG_GROUP_DONE` reported: `I` idle, `T` in a transaction, `E` in a failed one (as a byte). */
744
+ txStatus: i32 = 0
745
+ /** The number of groups sent and not yet answered. */
746
+ inFlight: i32 = 0
747
+ /** The server's process id, from BackendKeyData. */
748
+ backendPid: i32 = 0
749
+ /** The number of notices received, stopping at 2^31 - 1. */
750
+ notices: i32 = 0
751
+ /** The server's `server_version` parameter. */
752
+ serverVersion: string = ""
753
+ /** The last ErrorResponse, kept until the next. */
754
+ lastError: PgError | null = null
755
+ /** The last NoticeResponse, kept until the next. */
756
+ lastNotice: PgError | null = null
757
+ /** The decoders `int`, `bool` and `isNull` use; `text.failed` says whether one was given a column it could not read. */
758
+ readonly text: PgText
759
+
760
+ private readonly config: PgConfig
761
+ private readonly password: PgPassword
762
+ private state: i32 = 0
763
+ private input: u8[]
764
+ private inStart: i32 = 0
765
+ private inEnd: i32 = 0
766
+ private out: u8[]
767
+ private outStart: i32 = 0
768
+ private outEnd: i32 = 0
769
+ private colStarts: i32[]
770
+ private colLengths: i32[]
771
+ private colTypes: i32[]
772
+ private tagStart: i32 = 0
773
+ private tagLength: i32 = 0
774
+ private errorInGroup: boolean = false
775
+ private groupOpen: boolean = false
776
+ /** Whether a statement in the group `PG_GROUP_DONE` just finished failed. */
777
+ groupFailed: boolean = false
778
+ private bindAt: i32 = -1
779
+ private paramCountAt: i32 = 0
780
+ private paramCount: i32 = 0
781
+ private clientNonce: string = ""
782
+ private serverSignature: u8[]
783
+
784
+ /**
785
+ * A connection that has queued its startup message. `password` is copied,
786
+ * so the caller may wipe its own; `nonce` must be at least 18 bytes from a
787
+ * cryptographic random source (`crypto.getRandomValues`), and is fixed only
788
+ * in a test.
789
+ */
790
+ constructor(config: PgConfig, password: Secret<u8[]>, nonce: u8[]) {
791
+ if (toI32(nonce.length) < PG_NONCE_BYTES) {
792
+ panic("nish/net/postgres: the SCRAM nonce needs at least 18 random bytes")
793
+ }
794
+ pgCheckConfig(config)
795
+ this.config = config
796
+ this.text = new PgText()
797
+ this.password = new PgPassword(password)
798
+ const inputSize: i32 = config.inputSize < 64 ? 64 : config.inputSize
799
+ this.input = new Array<u8>(inputSize)
800
+ this.out = new Array<u8>(1024)
801
+ this.colStarts = new Array<i32>(16)
802
+ this.colLengths = new Array<i32>(16)
803
+ this.colTypes = new Array<i32>(16)
804
+ this.serverSignature = []
805
+ const head: u8[] = []
806
+ for (let i: i32 = 0; i < PG_NONCE_BYTES && i < toI32(nonce.length); i++) {
807
+ head.push(nonce[i])
808
+ }
809
+ this.clientNonce = base64StdPadEncode(head)
810
+
811
+ const at: i32 = this.outEnd
812
+ this.putI32(PG_ZERO)
813
+ this.putI32(PG_PROTOCOL)
814
+ this.putParam("user", config.user)
815
+ if (config.database.length > 0) {
816
+ this.putParam("database", config.database)
817
+ }
818
+ this.putParam("client_encoding", "UTF8")
819
+ this.putParam("DateStyle", "ISO")
820
+ this.putParam("TimeZone", "UTC")
821
+ if (config.applicationName.length > 0) {
822
+ this.putParam("application_name", config.applicationName)
823
+ }
824
+ if (config.options.length > 0) {
825
+ this.putParam("options", config.options)
826
+ }
827
+ this.putByte(PG_ZERO)
828
+ setI32BE(this.out, at, this.outEnd - at)
829
+ }
830
+
831
+ // --- Buffers -----------------------------------------------------------------
832
+
833
+ /** The input buffer: read from the socket into it at `inputEnd()`. */
834
+ inputBuffer(): u8[] {
835
+ return this.input
836
+ }
837
+
838
+ /** Where the next byte read goes. */
839
+ inputEnd(): i32 {
840
+ return this.inEnd
841
+ }
842
+
843
+ /** How many bytes may be read into the input buffer now. */
844
+ inputRoom(): i32 {
845
+ return toI32(this.input.length) - this.inEnd
846
+ }
847
+
848
+ /** Records that `n` bytes were read into the input buffer at `inputEnd()`. */
849
+ received(n: i32): void {
850
+ if (n < 0 || n > this.inputRoom()) {
851
+ panic("nish/net/postgres: received more than inputRoom()")
852
+ }
853
+ this.inEnd += n
854
+ }
855
+
856
+ /** Copies what fits of `src[start, start + length)` into the input buffer; answers how much. */
857
+ receive(src: u8[], start: i32, length: i32): i32 {
858
+ if (start < 0 || length < 0 || length > toI32(src.length) - start) {
859
+ panic("nish/net/postgres: receive() outside its source")
860
+ }
861
+ const room: i32 = this.inputRoom()
862
+ const n: i32 = length < room ? length : room
863
+ copyBytes(this.input, this.inEnd, src, start, n)
864
+ this.inEnd += n
865
+ return n
866
+ }
867
+
868
+ /** The output buffer: write `[outputStart(), outputEnd())` to the socket. */
869
+ outputBuffer(): u8[] {
870
+ return this.out
871
+ }
872
+
873
+ /** The first byte not yet sent. */
874
+ outputStart(): i32 {
875
+ return this.outStart
876
+ }
877
+
878
+ /** One past the last byte that may be sent: a Bind still open is held back until `bindEnd()` has written its lengths. */
879
+ outputEnd(): i32 {
880
+ return this.bindAt >= 0 ? this.bindAt : this.outEnd
881
+ }
882
+
883
+ /** Records that `n` bytes from `outputStart()` were written to the socket. */
884
+ sent(n: i32): void {
885
+ if (n < 0 || n > this.outputEnd() - this.outStart) {
886
+ panic("nish/net/postgres: sent more than was queued")
887
+ }
888
+ this.outStart += n
889
+ if (this.outStart === this.outEnd) {
890
+ this.outStart = 0
891
+ this.outEnd = 0
892
+ } else if (this.outStart >= toI32(this.out.length) / 2) {
893
+ this.compactOutput()
894
+ }
895
+ }
896
+
897
+ /** Reports the end of the stream: unless it already has, the connection fails with `PG_ERR_CLOSED`. */
898
+ closed(): i32 {
899
+ if (this.state !== PG_STATE_FAILED) {
900
+ return this.fail(PG_ERR_CLOSED)
901
+ }
902
+ return PG_FAILED
903
+ }
904
+
905
+ /**
906
+ * Whether every byte of the connection's copy of the password is zero: true
907
+ * once the proof is computed, the connection has failed or `dispose()` has
908
+ * run (and for an empty password). It reads the copy itself, so a test can
909
+ * see the wipe happen; a program has no use for it.
910
+ */
911
+ passwordWiped(): boolean {
912
+ return this.password.wiped()
913
+ }
914
+
915
+ /** Wipes the password if the connection still holds it. A program that drops a connection before it is ready calls this. */
916
+ dispose(): void {
917
+ if (!this.password.gone) {
918
+ this.password.wipe()
919
+ }
920
+ }
921
+
922
+ // --- Reading -----------------------------------------------------------------
923
+
924
+ /** The next event from the input. */
925
+ next(): i32 {
926
+ while (true) {
927
+ if (this.state === PG_STATE_FAILED) {
928
+ return PG_FAILED
929
+ }
930
+ const avail: i32 = this.inEnd - this.inStart
931
+ if (avail < 5) {
932
+ this.compact()
933
+ return PG_NEED_MORE
934
+ }
935
+ const at: i32 = this.inStart
936
+ const b: u8[] = this.input
937
+ // inStart and inEnd stay within the buffer, so this test never fails; it proves the indices.
938
+ if (at < 0 || at + 4 >= toI32(b.length)) {
939
+ return this.fail(PG_ERR_FRAME)
940
+ }
941
+ const type: i32 = toI32(b[at])
942
+ // getI32BE written out, as in readRow: this runs once per message.
943
+ const length: i32 =
944
+ (toI32(b[at + 1]) << 24) | (toI32(b[at + 2]) << 16) | (toI32(b[at + 3]) << 8) | toI32(b[at + 4])
945
+ if (length < 4) {
946
+ return this.fail(PG_ERR_FRAME)
947
+ }
948
+ if (length > this.config.maxMessage) {
949
+ return this.fail(PG_ERR_TOO_BIG)
950
+ }
951
+ if (avail - 1 < length) {
952
+ this.compact()
953
+ const size: i32 = toI32(this.input.length)
954
+ if (size < length + 1) {
955
+ // Doubled, up to one message of maxMessage, so that a run of growing messages copies each byte a bounded number of times.
956
+ const cap: i32 = this.config.maxMessage + 1
957
+ const doubled: i32 = size < cap / 2 ? size * 2 : cap
958
+ const grown: u8[] = new Array<u8>(doubled > length + 1 ? doubled : length + 1)
959
+ grown.set(this.input, 0)
960
+ this.input = grown
961
+ }
962
+ return PG_NEED_MORE
963
+ }
964
+ this.inStart = at + 1 + length
965
+ const event: i32 = this.dispatch(type, at + 5, at + 1 + length)
966
+ if (event !== PG_NEED_MORE) {
967
+ return event
968
+ }
969
+ }
970
+ }
971
+
972
+ /** Moves the unread input, at most one partial message, to the start of the buffer. */
973
+ private compact(): void {
974
+ const start: i32 = this.inStart
975
+ if (start === 0) {
976
+ return
977
+ }
978
+ const n: i32 = this.inEnd - start
979
+ copyBytes(this.input, 0, this.input, start, n)
980
+ this.inStart = 0
981
+ this.inEnd = n
982
+ }
983
+
984
+ /** Fails the connection: the password is wiped and every later `next()` answers `PG_FAILED`. */
985
+ private fail(code: i32): i32 {
986
+ this.state = PG_STATE_FAILED
987
+ this.failure = code
988
+ this.dispose()
989
+ wipe(this.serverSignature)
990
+ this.serverSignature = []
991
+ return PG_FAILED
992
+ }
993
+
994
+ /** One message, `[body, end)` its contents; answers its event, or `PG_NEED_MORE` when it has none. */
995
+ private dispatch(type: i32, body: i32, end: i32): i32 {
996
+ const state: i32 = this.state
997
+ if (type === 82) {
998
+ // 'R': an authentication request.
999
+ if (state >= PG_STATE_STARTING || end - body < 4) {
1000
+ return this.fail(end - body < 4 ? PG_ERR_FRAME : PG_ERR_UNEXPECTED)
1001
+ }
1002
+ return this.authenticate(getI32BE(this.input, body), body + 4, end)
1003
+ }
1004
+ if (type === 69 || type === 78) {
1005
+ // 'E' and 'N': an error or a notice.
1006
+ const error: PgError | null = this.readError(body, end)
1007
+ if (error === null) {
1008
+ return this.fail(PG_ERR_FRAME)
1009
+ }
1010
+ if (type === 78) {
1011
+ if (this.notices < 2147483647) {
1012
+ this.notices += 1
1013
+ }
1014
+ this.lastNotice = error
1015
+ return PG_NOTICE
1016
+ }
1017
+ this.lastError = error
1018
+ if (state < PG_STATE_READY || error.severity === "FATAL" || error.severity === "PANIC") {
1019
+ return this.fail(PG_ERR_SERVER)
1020
+ }
1021
+ if (this.inFlight === 0) {
1022
+ return this.fail(PG_ERR_UNEXPECTED)
1023
+ }
1024
+ this.errorInGroup = true
1025
+ return PG_ERROR
1026
+ }
1027
+ if (type === 83) {
1028
+ // 'S': ParameterStatus, name and value.
1029
+ const nameEnd: i32 = pgFind(this.input, body, end, 0)
1030
+ if (nameEnd === end || pgFind(this.input, nameEnd + 1, end, 0) !== end - 1) {
1031
+ return this.fail(PG_ERR_FRAME)
1032
+ }
1033
+ if (pgBytesAre(this.input, body, nameEnd - body, "client_encoding")) {
1034
+ if (!pgBytesAre(this.input, nameEnd + 1, end - 2 - nameEnd, "UTF8")) {
1035
+ return this.fail(PG_ERR_ENCODING)
1036
+ }
1037
+ } else if (pgBytesAre(this.input, body, nameEnd - body, "DateStyle")) {
1038
+ // "ISO, MDY": the output style comes first, the input order after the comma.
1039
+ const valueLength: i32 = end - 2 - nameEnd
1040
+ const iso: boolean = valueLength >= 3 && pgBytesAre(this.input, nameEnd + 1, 3, "ISO")
1041
+ if (!iso || (valueLength > 3 && toI32(this.input[nameEnd + 4]) !== PG_COMMA)) {
1042
+ return this.fail(PG_ERR_SETTING)
1043
+ }
1044
+ } else if (pgBytesAre(this.input, body, nameEnd - body, "TimeZone")) {
1045
+ if (!pgBytesAre(this.input, nameEnd + 1, end - 2 - nameEnd, "UTC")) {
1046
+ return this.fail(PG_ERR_SETTING)
1047
+ }
1048
+ } else if (pgBytesAre(this.input, body, nameEnd - body, "server_version")) {
1049
+ this.serverVersion = String.fromBytes(this.input, nameEnd + 1, end - 1)
1050
+ }
1051
+ return PG_NEED_MORE
1052
+ }
1053
+ if (type === 65) {
1054
+ // 'A': a notification, for a LISTEN this client never sends.
1055
+ return PG_NEED_MORE
1056
+ }
1057
+ if (state < PG_STATE_STARTING) {
1058
+ return this.fail(PG_ERR_UNEXPECTED)
1059
+ }
1060
+ if (type === 75) {
1061
+ // 'K': BackendKeyData, the process id and the cancel key.
1062
+ if (state !== PG_STATE_STARTING || end - body !== 8) {
1063
+ return this.fail(state !== PG_STATE_STARTING ? PG_ERR_UNEXPECTED : PG_ERR_FRAME)
1064
+ }
1065
+ this.backendPid = getI32BE(this.input, body)
1066
+ return PG_NEED_MORE
1067
+ }
1068
+ if (type === 90) {
1069
+ // 'Z': ReadyForQuery, and the transaction status.
1070
+ if (end - body !== 1) {
1071
+ return this.fail(PG_ERR_FRAME)
1072
+ }
1073
+ const status: i32 = toI32(this.input[body])
1074
+ if (status !== 73 && status !== 84 && status !== 69) {
1075
+ return this.fail(PG_ERR_FRAME)
1076
+ }
1077
+ this.txStatus = status
1078
+ if (state === PG_STATE_STARTING) {
1079
+ this.state = PG_STATE_READY
1080
+ return PG_READY
1081
+ }
1082
+ if (this.inFlight === 0 || (this.inFlight === 1 && this.groupOpen)) {
1083
+ return this.fail(PG_ERR_UNEXPECTED)
1084
+ }
1085
+ this.inFlight -= 1
1086
+ this.groupFailed = this.errorInGroup
1087
+ this.errorInGroup = false
1088
+ return PG_GROUP_DONE
1089
+ }
1090
+ if (state !== PG_STATE_READY || this.inFlight === 0) {
1091
+ return this.fail(PG_ERR_UNEXPECTED)
1092
+ }
1093
+ if (type === 68) {
1094
+ return this.readRow(body, end)
1095
+ }
1096
+ if (type === 67) {
1097
+ // 'C': CommandComplete, the tag.
1098
+ if (end - body < 1 || pgFind(this.input, body, end, 0) !== end - 1) {
1099
+ return this.fail(PG_ERR_FRAME)
1100
+ }
1101
+ this.tagStart = body
1102
+ this.tagLength = end - 1 - body
1103
+ return PG_COMPLETE
1104
+ }
1105
+ if (type === 73) {
1106
+ // 'I': EmptyQueryResponse, an empty statement's completion.
1107
+ if (end !== body) {
1108
+ return this.fail(PG_ERR_FRAME)
1109
+ }
1110
+ this.tagStart = body
1111
+ this.tagLength = 0
1112
+ return PG_COMPLETE
1113
+ }
1114
+ if (type === 49 || type === 50 || type === 51) {
1115
+ // '1', '2', '3': ParseComplete, BindComplete, CloseComplete.
1116
+ return end === body ? PG_NEED_MORE : this.fail(PG_ERR_FRAME)
1117
+ }
1118
+ if (type === 116) {
1119
+ // 't': ParameterDescription.
1120
+ if (end - body < 2 || end - body !== toI32(getU16BE(this.input, body)) * 4 + 2) {
1121
+ return this.fail(PG_ERR_FRAME)
1122
+ }
1123
+ return PG_NEED_MORE
1124
+ }
1125
+ if (type === 84) {
1126
+ return this.readDescription(body, end)
1127
+ }
1128
+ if (type === 110) {
1129
+ // 'n': NoData, a statement that returns no rows.
1130
+ if (end !== body) {
1131
+ return this.fail(PG_ERR_FRAME)
1132
+ }
1133
+ this.columns = 0
1134
+ return PG_DESCRIBED
1135
+ }
1136
+ return this.fail(PG_ERR_UNEXPECTED)
1137
+ }
1138
+
1139
+ /** Makes the column arrays hold at least `n`. */
1140
+ private reserveColumns(n: i32): void {
1141
+ if (n <= toI32(this.colStarts.length)) {
1142
+ return
1143
+ }
1144
+ const size: i32 = n < 2 * toI32(this.colStarts.length) ? 2 * toI32(this.colStarts.length) : n
1145
+ const starts: i32[] = new Array<i32>(size)
1146
+ const lengths: i32[] = new Array<i32>(size)
1147
+ const types: i32[] = new Array<i32>(size)
1148
+ starts.set(this.colStarts, 0)
1149
+ lengths.set(this.colLengths, 0)
1150
+ types.set(this.colTypes, 0)
1151
+ this.colStarts = starts
1152
+ this.colLengths = lengths
1153
+ this.colTypes = types
1154
+ }
1155
+
1156
+ /** 'D': DataRow, each column's length (-1 for NULL) and bytes, which must fill the message exactly. */
1157
+ private readRow(body: i32, end: i32): i32 {
1158
+ if (end - body < 2) {
1159
+ return this.fail(PG_ERR_FRAME)
1160
+ }
1161
+ const b: u8[] = this.input
1162
+ const n: i32 = toI32(getU16BE(b, body))
1163
+ if (n > this.config.maxColumns) {
1164
+ return this.fail(PG_ERR_TOO_BIG)
1165
+ }
1166
+ this.reserveColumns(n)
1167
+ const starts: i32[] = this.colStarts
1168
+ const lengths: i32[] = this.colLengths
1169
+ let p: i32 = body + 2
1170
+ // reserveColumns made both arrays hold n; the length tests prove the indices.
1171
+ for (let c: i32 = 0; c < n && c < toI32(starts.length) && c < toI32(lengths.length); c++) {
1172
+ // The buffer tests never fail on a frame next() cut from it; they prove the indices.
1173
+ if (end - p < 4 || p < 0 || p + 3 >= toI32(b.length)) {
1174
+ return this.fail(PG_ERR_FRAME)
1175
+ }
1176
+ // getI32BE written out: a call per column was a fifth of the row's cost.
1177
+ const length: i32 =
1178
+ (toI32(b[p]) << 24) | (toI32(b[p + 1]) << 16) | (toI32(b[p + 2]) << 8) | toI32(b[p + 3])
1179
+ p += 4
1180
+ if (length < -1 || length > end - p) {
1181
+ return this.fail(PG_ERR_FRAME)
1182
+ }
1183
+ starts[c] = p
1184
+ lengths[c] = length
1185
+ if (length > 0) {
1186
+ p += length
1187
+ }
1188
+ }
1189
+ if (p !== end) {
1190
+ return this.fail(PG_ERR_FRAME)
1191
+ }
1192
+ this.columns = n
1193
+ return PG_ROW
1194
+ }
1195
+
1196
+ /** 'T': RowDescription; each field is a name and 18 bytes, the type's OID 6 bytes into them. */
1197
+ private readDescription(body: i32, end: i32): i32 {
1198
+ if (end - body < 2) {
1199
+ return this.fail(PG_ERR_FRAME)
1200
+ }
1201
+ const b: u8[] = this.input
1202
+ const n: i32 = toI32(getU16BE(b, body))
1203
+ if (n > this.config.maxColumns) {
1204
+ return this.fail(PG_ERR_TOO_BIG)
1205
+ }
1206
+ this.reserveColumns(n)
1207
+ let p: i32 = body + 2
1208
+ for (let c: i32 = 0; c < n; c++) {
1209
+ const nameEnd: i32 = pgFind(b, p, end, 0)
1210
+ if (end - nameEnd < 19) {
1211
+ return this.fail(PG_ERR_FRAME)
1212
+ }
1213
+ this.colTypes[c] = getI32BE(b, nameEnd + 7)
1214
+ p = nameEnd + 19
1215
+ }
1216
+ if (p !== end) {
1217
+ return this.fail(PG_ERR_FRAME)
1218
+ }
1219
+ this.columns = n
1220
+ return PG_DESCRIBED
1221
+ }
1222
+
1223
+ /** 'E' and 'N': the fields, each a type byte and a string, then a 0; `null` when they do not fill the message exactly. */
1224
+ private readError(body: i32, end: i32): PgError | null {
1225
+ const b: u8[] = this.input
1226
+ if (body < 0 || end > toI32(b.length)) {
1227
+ return null
1228
+ }
1229
+ let severity: string = ""
1230
+ let localised: string = ""
1231
+ let code: string = ""
1232
+ let message: string = ""
1233
+ let detail: string = ""
1234
+ let hint: string = ""
1235
+ let constraint: string = ""
1236
+ let position: string = ""
1237
+ let p: i32 = body
1238
+ while (true) {
1239
+ if (p < 0 || p >= end || p >= toI32(b.length)) {
1240
+ return null
1241
+ }
1242
+ const field: i32 = toI32(b[p])
1243
+ if (field === 0) {
1244
+ if (p !== end - 1) {
1245
+ return null
1246
+ }
1247
+ break
1248
+ }
1249
+ const valueEnd: i32 = pgFind(b, p + 1, end, 0)
1250
+ if (valueEnd === end) {
1251
+ return null
1252
+ }
1253
+ const value: string = String.fromBytes(b, p + 1, valueEnd)
1254
+ if (field === 86) {
1255
+ severity = value
1256
+ } else if (field === 83) {
1257
+ localised = value
1258
+ } else if (field === 67) {
1259
+ code = value
1260
+ } else if (field === 77) {
1261
+ message = value
1262
+ } else if (field === 68) {
1263
+ detail = value
1264
+ } else if (field === 72) {
1265
+ hint = value
1266
+ } else if (field === 110) {
1267
+ constraint = value
1268
+ } else if (field === 80) {
1269
+ position = value
1270
+ }
1271
+ p = valueEnd + 1
1272
+ }
1273
+ return new PgError(
1274
+ severity.length > 0 ? severity : localised,
1275
+ code,
1276
+ message,
1277
+ detail,
1278
+ hint,
1279
+ constraint,
1280
+ position
1281
+ )
1282
+ }
1283
+
1284
+ // --- Authentication ----------------------------------------------------------
1285
+
1286
+ /** 'R': the authentication request `code`, its data at `[body, end)`. */
1287
+ private authenticate(code: i32, body: i32, end: i32): i32 {
1288
+ const state: i32 = this.state
1289
+ if (code === 0) {
1290
+ // AuthenticationOk: without a password, or after the server proved it knows it.
1291
+ if (
1292
+ state === PG_STATE_SCRAM_FIRST ||
1293
+ (state === PG_STATE_SCRAM_FINAL && this.serverSignature.length > 0)
1294
+ ) {
1295
+ return this.fail(PG_ERR_SCRAM)
1296
+ }
1297
+ if (end !== body) {
1298
+ return this.fail(PG_ERR_FRAME)
1299
+ }
1300
+ this.dispose()
1301
+ this.state = PG_STATE_STARTING
1302
+ return PG_NEED_MORE
1303
+ }
1304
+ if (code === 10 && state === PG_STATE_AUTH) {
1305
+ return this.scramStart(body, end)
1306
+ }
1307
+ if (code === 11 && state === PG_STATE_SCRAM_FIRST) {
1308
+ return this.scramContinue(body, end)
1309
+ }
1310
+ if (code === 12 && state === PG_STATE_SCRAM_FINAL) {
1311
+ return this.scramFinal(body, end)
1312
+ }
1313
+ if (state === PG_STATE_AUTH && code !== 10 && code !== 11 && code !== 12) {
1314
+ return this.fail(PG_ERR_AUTH_UNSUPPORTED)
1315
+ }
1316
+ return this.fail(PG_ERR_UNEXPECTED)
1317
+ }
1318
+
1319
+ /** AuthenticationSASL: the mechanisms, each NUL-terminated, then a 0; SCRAM-SHA-256 must be among them. */
1320
+ private scramStart(body: i32, end: i32): i32 {
1321
+ const b: u8[] = this.input
1322
+ if (body < 0 || end > toI32(b.length)) {
1323
+ return this.fail(PG_ERR_FRAME)
1324
+ }
1325
+ let offered: boolean = false
1326
+ let p: i32 = body
1327
+ while (true) {
1328
+ if (p < 0 || p >= end || p >= toI32(b.length)) {
1329
+ return this.fail(PG_ERR_FRAME)
1330
+ }
1331
+ if (toI32(b[p]) === 0) {
1332
+ if (p !== end - 1) {
1333
+ return this.fail(PG_ERR_FRAME)
1334
+ }
1335
+ break
1336
+ }
1337
+ const nameEnd: i32 = pgFind(b, p, end, 0)
1338
+ if (nameEnd === end) {
1339
+ return this.fail(PG_ERR_FRAME)
1340
+ }
1341
+ if (pgBytesAre(b, p, nameEnd - p, "SCRAM-SHA-256")) {
1342
+ offered = true
1343
+ }
1344
+ p = nameEnd + 1
1345
+ }
1346
+ if (!offered) {
1347
+ return this.fail(PG_ERR_AUTH_UNSUPPORTED)
1348
+ }
1349
+ // SASLInitialResponse: the mechanism, then client-first-message, `n,,`
1350
+ // and the bare part. PostgreSQL takes the user from the startup message,
1351
+ // so the SCRAM user name is empty (§53.3.1).
1352
+ const first: string = `n,,${this.firstBare()}`
1353
+ const at: i32 = this.startMessage(PG_FE_PASSWORD)
1354
+ this.putCString("SCRAM-SHA-256")
1355
+ this.putI32(toI32(first.length))
1356
+ this.putString(first)
1357
+ this.endMessage(at)
1358
+ this.state = PG_STATE_SCRAM_FIRST
1359
+ return PG_NEED_MORE
1360
+ }
1361
+
1362
+ /** client-first-message-bare. */
1363
+ private firstBare(): string {
1364
+ return `n=,r=${this.clientNonce}`
1365
+ }
1366
+
1367
+ /**
1368
+ * AuthenticationSASLContinue: server-first-message, exactly `r=`, `s=`,
1369
+ * `i=` in that order. Answers with client-final-message and its proof.
1370
+ */
1371
+ private scramContinue(body: i32, end: i32): i32 {
1372
+ const b: u8[] = this.input
1373
+ if (body < 0 || end > toI32(b.length)) {
1374
+ return this.fail(PG_ERR_FRAME)
1375
+ }
1376
+ const nonceEnd: i32 = pgFind(b, body, end, 44)
1377
+ const saltEnd: i32 = pgFind(b, nonceEnd + 1 < end ? nonceEnd + 1 : end, end, 44)
1378
+ if (
1379
+ nonceEnd === end ||
1380
+ saltEnd === end ||
1381
+ !pgBytesAre(b, body, 2, "r=") ||
1382
+ !pgBytesAre(b, nonceEnd + 1, 2, "s=") ||
1383
+ end - saltEnd < 4 ||
1384
+ !pgBytesAre(b, saltEnd + 1, 2, "i=")
1385
+ ) {
1386
+ return this.fail(PG_ERR_SCRAM)
1387
+ }
1388
+ // The nonce extends ours with printable characters other than a comma.
1389
+ const ours: i32 = toI32(this.clientNonce.length)
1390
+ if (nonceEnd - body - 2 <= ours || !pgBytesAre(b, body + 2, ours, this.clientNonce)) {
1391
+ return this.fail(PG_ERR_SCRAM)
1392
+ }
1393
+ const nonceStart: i32 = body + 2 + ours
1394
+ const digitsStart: i32 = saltEnd + 3
1395
+ if (nonceStart < 0 || nonceEnd > toI32(b.length)) {
1396
+ return this.fail(PG_ERR_SCRAM)
1397
+ }
1398
+ for (let i: i32 = nonceStart; i < nonceEnd && i < toI32(b.length); i++) {
1399
+ const c: i32 = toI32(b[i])
1400
+ if (c < 33 || c > 126) {
1401
+ return this.fail(PG_ERR_SCRAM)
1402
+ }
1403
+ }
1404
+ if (digitsStart < 0) {
1405
+ return this.fail(PG_ERR_SCRAM)
1406
+ }
1407
+ let iterations: i32 = 0
1408
+ for (let i: i32 = digitsStart; i < end && i < toI32(b.length); i++) {
1409
+ const d: i32 = toI32(b[i]) - 48
1410
+ if (d < 0 || d > 9 || iterations > this.config.maxIterations) {
1411
+ return this.fail(PG_ERR_SCRAM)
1412
+ }
1413
+ iterations = iterations * 10 + d
1414
+ }
1415
+ if (iterations < this.config.minIterations || iterations > this.config.maxIterations) {
1416
+ return this.fail(PG_ERR_SCRAM)
1417
+ }
1418
+ const salt: u8[] | null = base64StdPadDecode(String.fromBytes(b, nonceEnd + 3, saltEnd))
1419
+ if (salt === null || salt.length === 0) {
1420
+ return this.fail(PG_ERR_SCRAM)
1421
+ }
1422
+ const password: PgPassword = this.password
1423
+ if (password.gone) {
1424
+ return this.fail(PG_ERR_SCRAM)
1425
+ }
1426
+ const salted: Secret<u8[]> | null = pgSaltedPassword(password.bytes, salt, iterations)
1427
+ if (salted === null) {
1428
+ return this.fail(PG_ERR_SCRAM)
1429
+ }
1430
+ const withoutProof: string = `c=biws,r=${String.fromBytes(b, body + 2, nonceEnd)}`
1431
+ const authMessage: u8[] = pgBytesOf(
1432
+ `${this.firstBare()},${String.fromBytes(b, body, end)},${withoutProof}`
1433
+ )
1434
+ const keys: u8[] = exposeWith(salted, authMessage, pgScramKeys)
1435
+ wipe(salted)
1436
+ this.dispose()
1437
+ const proof: u8[] = new Array<u8>(32)
1438
+ const signature: u8[] = new Array<u8>(32)
1439
+ if (toI32(keys.length) < 64) {
1440
+ panic("nish/net/postgres: the SCRAM keys are not 64 bytes")
1441
+ }
1442
+ for (let i: i32 = 0; i < 32; i++) {
1443
+ proof[i] = keys[i]
1444
+ signature[i] = keys[32 + i]
1445
+ }
1446
+ wipe(keys)
1447
+ this.serverSignature = signature
1448
+ // The proof goes on the wire as it is, so its base64 string is no secret.
1449
+ const encoded: string = base64StdPadEncode(proof)
1450
+ wipe(proof)
1451
+ const at: i32 = this.startMessage(PG_FE_PASSWORD)
1452
+ this.putString(`${withoutProof},p=${encoded}`)
1453
+ this.endMessage(at)
1454
+ this.state = PG_STATE_SCRAM_FINAL
1455
+ return PG_NEED_MORE
1456
+ }
1457
+
1458
+ /** AuthenticationSASLFinal: exactly `v=` and the ServerSignature, compared in constant time. */
1459
+ private scramFinal(body: i32, end: i32): i32 {
1460
+ const b: u8[] = this.input
1461
+ if (end - body < 2 || !pgBytesAre(b, body, 2, "v=")) {
1462
+ return this.fail(PG_ERR_SCRAM)
1463
+ }
1464
+ const signature: u8[] | null = base64StdPadDecode(String.fromBytes(b, body + 2, end))
1465
+ if (signature === null || signature.length !== 32) {
1466
+ return this.fail(PG_ERR_SCRAM)
1467
+ }
1468
+ if (!timingSafeEqual(signature, this.serverSignature)) {
1469
+ return this.fail(PG_ERR_SERVER_SIGNATURE)
1470
+ }
1471
+ wipe(this.serverSignature)
1472
+ this.serverSignature = []
1473
+ return PG_NEED_MORE
1474
+ }
1475
+
1476
+ // --- Writing -----------------------------------------------------------------
1477
+
1478
+ /**
1479
+ * Whether a builder call may queue `size` more bytes and still leave
1480
+ * `PG_TAIL` under `maxOutput`. When they would not fit after the end of the
1481
+ * buffer, the unsent bytes move to its start first: a builder call asks
1482
+ * before it takes any offset into the buffer, so nothing it holds moves.
1483
+ */
1484
+ private room(size: i32): boolean {
1485
+ const queued: i32 = this.outEnd - this.outStart
1486
+ if (size < 0 || size > this.config.maxOutput - PG_TAIL - queued) {
1487
+ return false
1488
+ }
1489
+ if (this.outStart > 0 && this.outEnd + size > toI32(this.out.length)) {
1490
+ this.compactOutput()
1491
+ }
1492
+ return true
1493
+ }
1494
+
1495
+ /**
1496
+ * Makes room for the `size` bytes of a tail message (`bindEnd`, `sync`,
1497
+ * `terminate`) the way `room` does for the rest, so that the buffer is not
1498
+ * grown past `maxOutput` while sent bytes still fill its start.
1499
+ */
1500
+ private tailRoom(size: i32): void {
1501
+ if (this.outStart > 0 && this.outEnd + size > toI32(this.out.length)) {
1502
+ this.compactOutput()
1503
+ }
1504
+ }
1505
+
1506
+ /** Moves the unsent output to the start of the buffer, the open bind's offsets with it. */
1507
+ private compactOutput(): void {
1508
+ const shift: i32 = this.outStart
1509
+ if (shift === 0) {
1510
+ return
1511
+ }
1512
+ copyBytes(this.out, 0, this.out, shift, this.outEnd - shift)
1513
+ this.outEnd -= shift
1514
+ this.outStart = 0
1515
+ if (this.bindAt >= 0) {
1516
+ this.bindAt -= shift
1517
+ this.paramCountAt -= shift
1518
+ }
1519
+ }
1520
+
1521
+ /** Makes the output buffer at least `want` bytes long. */
1522
+ private reserve(want: i32): void {
1523
+ const size: i32 = toI32(this.out.length)
1524
+ if (want <= size) {
1525
+ return
1526
+ }
1527
+ const grown: u8[] = new Array<u8>(size <= PG_MAX_CAP && want < 2 * size ? 2 * size : want)
1528
+ grown.set(this.out, 0)
1529
+ this.out = grown
1530
+ }
1531
+
1532
+ private putByte(v: i32): void {
1533
+ this.reserve(this.outEnd + 1)
1534
+ this.out[this.outEnd] = toU8(v)
1535
+ this.outEnd += 1
1536
+ }
1537
+
1538
+ private putI32(v: i32): void {
1539
+ this.reserve(this.outEnd + 4)
1540
+ setI32BE(this.out, this.outEnd, v)
1541
+ this.outEnd += 4
1542
+ }
1543
+
1544
+ private putI16(v: i32): void {
1545
+ this.reserve(this.outEnd + 2)
1546
+ setU16BE(this.out, this.outEnd, toU16(v))
1547
+ this.outEnd += 2
1548
+ }
1549
+
1550
+ private putString(text: string): void {
1551
+ const n: i32 = toI32(text.length)
1552
+ this.reserve(this.outEnd + n)
1553
+ for (let i: i32 = 0; i < n; i++) {
1554
+ this.out[this.outEnd + i] = toU8(text.charCodeAt(i))
1555
+ }
1556
+ this.outEnd += n
1557
+ }
1558
+
1559
+ private putCString(text: string): void {
1560
+ this.putString(text)
1561
+ this.putByte(PG_ZERO)
1562
+ }
1563
+
1564
+ private putParam(name: string, value: string): void {
1565
+ this.putCString(name)
1566
+ this.putCString(value)
1567
+ }
1568
+
1569
+ /** Starts a message of `type`; answers where, for `endMessage`. */
1570
+ private startMessage(type: i32): i32 {
1571
+ const at: i32 = this.outEnd
1572
+ this.putByte(type)
1573
+ this.putI32(PG_ZERO)
1574
+ return at
1575
+ }
1576
+
1577
+ /** Writes the length of the message started at `at`. */
1578
+ private endMessage(at: i32): void {
1579
+ setI32BE(this.out, at + 1, this.outEnd - at - 1)
1580
+ }
1581
+
1582
+ /** Whether another group may start, or the open one take another message. */
1583
+ canSend(): boolean {
1584
+ return this.state === PG_STATE_READY && (this.groupOpen || this.inFlight < this.config.maxInFlight)
1585
+ }
1586
+
1587
+ /** Opens a group if none is, for a message of `size` bytes; false, and no group opened, when none may be or there is no room. */
1588
+ private canOpen(size: i32): boolean {
1589
+ if (!this.canSend() || this.bindAt >= 0 || !this.room(size)) {
1590
+ return false
1591
+ }
1592
+ if (!this.groupOpen) {
1593
+ this.groupOpen = true
1594
+ this.inFlight += 1
1595
+ }
1596
+ return true
1597
+ }
1598
+
1599
+ /**
1600
+ * Parse and Describe: prepares `sql` as the statement `name` ("" is the
1601
+ * unnamed one), its parameters' types given by `paramTypes` (OIDs, 0 to let
1602
+ * the server infer one, and parameters past the array are inferred too).
1603
+ * The server answers `PG_DESCRIBED`. False, and nothing written, when
1604
+ * `canSend()` is false, a bind is open, `name` or `sql` holds a NUL, or the
1605
+ * message would pass `maxOutput`.
1606
+ */
1607
+ prepare(name: string, sql: string, paramTypes: i32[]): boolean {
1608
+ const nameLength: i32 = toI32(name.length)
1609
+ const types: i32 = toI32(paramTypes.length)
1610
+ if (pgHasNul(name) || pgHasNul(sql) || types > 65535 || nameLength > PG_MAX_CAP / 4) {
1611
+ return false
1612
+ }
1613
+ const sqlLength: i32 = toI32(sql.length)
1614
+ if (sqlLength > PG_MAX_CAP || !this.canOpen(toI32(16) + 2 * nameLength + sqlLength + 4 * types)) {
1615
+ return false
1616
+ }
1617
+ const at: i32 = this.startMessage(PG_FE_PARSE)
1618
+ this.putCString(name)
1619
+ this.putCString(sql)
1620
+ this.putI16(toI32(paramTypes.length))
1621
+ for (const oid of paramTypes) {
1622
+ this.putI32(oid)
1623
+ }
1624
+ this.endMessage(at)
1625
+ const describe: i32 = this.startMessage(PG_FE_DESCRIBE)
1626
+ this.putByte(PG_TARGET_STATEMENT)
1627
+ this.putCString(name)
1628
+ this.endMessage(describe)
1629
+ return true
1630
+ }
1631
+
1632
+ /** Close: forgets the prepared statement `name`. False as `prepare` is. */
1633
+ closeStatement(name: string): boolean {
1634
+ if (pgHasNul(name) || toI32(name.length) > PG_MAX_CAP || !this.canOpen(7 + toI32(name.length))) {
1635
+ return false
1636
+ }
1637
+ const at: i32 = this.startMessage(PG_FE_CLOSE)
1638
+ this.putByte(PG_TARGET_STATEMENT)
1639
+ this.putCString(name)
1640
+ this.endMessage(at)
1641
+ return true
1642
+ }
1643
+
1644
+ /**
1645
+ * Starts a Bind of the prepared statement `name`; its parameters follow, in
1646
+ * order, then `bindEnd()`. False as `prepare` is.
1647
+ */
1648
+ bindStart(name: string): boolean {
1649
+ if (pgHasNul(name) || toI32(name.length) > PG_MAX_CAP || !this.canOpen(11 + toI32(name.length))) {
1650
+ return false
1651
+ }
1652
+ this.bindAt = this.startMessage(PG_FE_BIND)
1653
+ this.putByte(PG_ZERO)
1654
+ this.putCString(name)
1655
+ this.putI16(PG_ZERO)
1656
+ this.paramCountAt = this.outEnd
1657
+ this.putI16(PG_ZERO)
1658
+ this.paramCount = 0
1659
+ return true
1660
+ }
1661
+
1662
+ /**
1663
+ * Opens a parameter of `size` bytes, its length word aside, and answers
1664
+ * where its length goes; -1, and nothing written, when no bind is open, the
1665
+ * bind has 65535 parameters already, or there is no room under `maxOutput`.
1666
+ */
1667
+ private startParam(size: i32): i32 {
1668
+ if (this.bindAt < 0 || this.paramCount === 65535 || size > PG_MAX_CAP || !this.room(4 + size)) {
1669
+ return -1
1670
+ }
1671
+ this.paramCount += 1
1672
+ const at: i32 = this.outEnd
1673
+ this.putI32(PG_ZERO)
1674
+ return at
1675
+ }
1676
+
1677
+ private endParam(at: i32): void {
1678
+ setI32BE(this.out, at, this.outEnd - at - 4)
1679
+ }
1680
+
1681
+ // Every `param...` call answers false, and writes nothing, when no bind is
1682
+ // open, the bind has 65535 parameters already, or the parameter would pass
1683
+ // `maxOutput`; a program that checked `bindStart()` meets only the last.
1684
+
1685
+ /** A text parameter. */
1686
+ paramText(value: string): boolean {
1687
+ const at: i32 = this.startParam(toI32(value.length))
1688
+ if (at < 0) {
1689
+ return false
1690
+ }
1691
+ this.putString(value)
1692
+ this.endParam(at)
1693
+ return true
1694
+ }
1695
+
1696
+ /** A text parameter from `bytes[start, start + length)`, UTF-8; false too when the window is not inside `bytes`. */
1697
+ paramBytes(bytes: u8[], start: i32, length: i32): boolean {
1698
+ if (start < 0 || length < 0 || length > toI32(bytes.length) - start) {
1699
+ return false
1700
+ }
1701
+ const at: i32 = this.startParam(length)
1702
+ if (at < 0) {
1703
+ return false
1704
+ }
1705
+ this.reserve(this.outEnd + length)
1706
+ copyBytes(this.out, this.outEnd, bytes, start, length)
1707
+ this.outEnd += length
1708
+ this.endParam(at)
1709
+ return true
1710
+ }
1711
+
1712
+ /** A NULL parameter. */
1713
+ paramNull(): boolean {
1714
+ if (this.startParam(toI32(0)) < 0) {
1715
+ return false
1716
+ }
1717
+ setI32BE(this.out, this.outEnd - 4, -1)
1718
+ return true
1719
+ }
1720
+
1721
+ /** An integer parameter, in decimal. */
1722
+ paramInt(value: i64): boolean {
1723
+ const at: i32 = this.startParam(toI32(20))
1724
+ if (at < 0) {
1725
+ return false
1726
+ }
1727
+ this.reserve(this.outEnd + 20)
1728
+ // The digits are written from the least significant, then reversed; the
1729
+ // value is made negative first so that the most negative i64 has its digits.
1730
+ let v: i64 = value < 0 ? value : -value
1731
+ if (value < 0) {
1732
+ this.out[this.outEnd] = toU8(45)
1733
+ this.outEnd += 1
1734
+ }
1735
+ const first: i32 = this.outEnd
1736
+ while (true) {
1737
+ this.out[this.outEnd] = toU8(48 - toI32(v % 10))
1738
+ this.outEnd += 1
1739
+ v = v / 10
1740
+ if (v === 0) {
1741
+ break
1742
+ }
1743
+ }
1744
+ let lo: i32 = first
1745
+ let hi: i32 = this.outEnd - 1
1746
+ while (lo < hi) {
1747
+ const t: u8 = this.out[lo]
1748
+ this.out[lo] = this.out[hi]
1749
+ this.out[hi] = t
1750
+ lo++
1751
+ hi--
1752
+ }
1753
+ this.endParam(at)
1754
+ return true
1755
+ }
1756
+
1757
+ /** A `text[]` parameter: every element quoted, `"` and `\` escaped. */
1758
+ paramTextArray(items: string[]): boolean {
1759
+ // Its exact size first, so that room is asked for once and a refusal writes nothing.
1760
+ let size: i64 = 2
1761
+ for (const item of items) {
1762
+ size += toI64(item.length) + 3
1763
+ const n: i32 = toI32(item.length)
1764
+ for (let i: i32 = 0; i < n; i++) {
1765
+ const c: i32 = toI32(item.charCodeAt(i))
1766
+ if (c === 34 || c === 92) {
1767
+ size += 1
1768
+ }
1769
+ }
1770
+ }
1771
+ const at: i32 = this.startParam(size > toI64(PG_MAX_CAP) ? PG_MAX_CAP + 1 : toI32(size))
1772
+ if (at < 0) {
1773
+ return false
1774
+ }
1775
+ this.putByte(PG_LBRACE)
1776
+ let first: boolean = true
1777
+ for (const item of items) {
1778
+ if (!first) {
1779
+ this.putByte(PG_COMMA)
1780
+ }
1781
+ first = false
1782
+ this.putByte(PG_QUOTE)
1783
+ const n: i32 = toI32(item.length)
1784
+ for (let i: i32 = 0; i < n; i++) {
1785
+ const c: i32 = toI32(item.charCodeAt(i))
1786
+ if (c === 34 || c === 92) {
1787
+ this.putByte(PG_BACKSLASH)
1788
+ }
1789
+ this.putByte(c)
1790
+ }
1791
+ this.putByte(PG_QUOTE)
1792
+ }
1793
+ this.putByte(PG_RBRACE)
1794
+ this.endParam(at)
1795
+ return true
1796
+ }
1797
+
1798
+ /**
1799
+ * Ends the Bind (every result column in text) and queues its Execute, for
1800
+ * every row. False, and nothing written, when no bind is open; never for
1801
+ * room, which the calls before it kept.
1802
+ */
1803
+ bindEnd(): boolean {
1804
+ if (this.bindAt < 0) {
1805
+ return false
1806
+ }
1807
+ this.tailRoom(PG_TAIL)
1808
+ const at: i32 = this.bindAt
1809
+ setU16BE(this.out, this.paramCountAt, toU16(this.paramCount))
1810
+ this.putI16(PG_ZERO)
1811
+ this.endMessage(at)
1812
+ this.bindAt = -1
1813
+ const execute: i32 = this.startMessage(PG_FE_EXECUTE)
1814
+ this.putByte(PG_ZERO)
1815
+ this.putI32(PG_ZERO)
1816
+ this.endMessage(execute)
1817
+ return true
1818
+ }
1819
+
1820
+ /** Sync: ends the open group, which the server answers with `PG_GROUP_DONE`. False when no group is open or a bind is; never for room. */
1821
+ sync(): boolean {
1822
+ if (!this.groupOpen || this.bindAt >= 0) {
1823
+ return false
1824
+ }
1825
+ this.groupOpen = false
1826
+ this.tailRoom(toI32(5))
1827
+ const at: i32 = this.startMessage(PG_FE_SYNC)
1828
+ this.endMessage(at)
1829
+ return true
1830
+ }
1831
+
1832
+ /** Terminate: tells the server the connection is ending; close the socket once it is sent. A bind still open is dropped unsent. */
1833
+ terminate(): void {
1834
+ if (this.bindAt >= 0) {
1835
+ this.outEnd = this.bindAt
1836
+ this.bindAt = -1
1837
+ }
1838
+ this.tailRoom(toI32(5))
1839
+ const at: i32 = this.startMessage(PG_FE_TERMINATE)
1840
+ this.endMessage(at)
1841
+ }
1842
+
1843
+ // --- Results -----------------------------------------------------------------
1844
+
1845
+ /** Column `c`'s first byte in `inputBuffer()`. */
1846
+ colStart(c: i32): i32 {
1847
+ return this.colStarts[c]
1848
+ }
1849
+
1850
+ /** Column `c`'s length, -1 for NULL. */
1851
+ colLength(c: i32): i32 {
1852
+ return this.colLengths[c]
1853
+ }
1854
+
1855
+ /** Whether column `c` is NULL. */
1856
+ isNull(c: i32): boolean {
1857
+ return this.colLengths[c] < 0
1858
+ }
1859
+
1860
+ /** Column `c` as a string; the one result read that allocates. "" for NULL. */
1861
+ columnText(c: i32): string {
1862
+ const length: i32 = this.colLengths[c]
1863
+ if (length <= 0) {
1864
+ return ""
1865
+ }
1866
+ const start: i32 = this.colStarts[c]
1867
+ return String.fromBytes(this.input, start, start + length)
1868
+ }
1869
+
1870
+ /** Column `c` of the statement just described: its type's OID (23 is `int4`, 25 `text`). */
1871
+ columnType(c: i32): i32 {
1872
+ return this.colTypes[c]
1873
+ }
1874
+
1875
+ /** Column `c` as an integer; NULL or a bad value sets `text.failed`. */
1876
+ int(c: i32): i64 {
1877
+ return this.text.int(this.input, this.colStarts[c], this.colLengths[c])
1878
+ }
1879
+
1880
+ /** Column `c` as a boolean; NULL or a bad value sets `text.failed`. */
1881
+ bool(c: i32): boolean {
1882
+ return this.text.bool(this.input, this.colStarts[c], this.colLengths[c])
1883
+ }
1884
+
1885
+ /** Whether the tag of the execution just completed starts with the word `word` (`INSERT`, `SELECT`, ...). */
1886
+ tagIs(word: string): boolean {
1887
+ const n: i32 = toI32(word.length)
1888
+ if (n > this.tagLength || !pgBytesAre(this.input, this.tagStart, n, word)) {
1889
+ return false
1890
+ }
1891
+ return n === this.tagLength || toI32(this.input[this.tagStart + n]) === 32
1892
+ }
1893
+
1894
+ /** The row count the tag ends with (`INSERT 0 3` is 3, `SELECT 5` is 5), or -1 when it has none or it does not fit an i64. */
1895
+ tagRows(): i64 {
1896
+ const end: i32 = this.tagStart + this.tagLength
1897
+ let p: i32 = end
1898
+ while (p > this.tagStart && toI32(this.input[p - 1]) >= 48 && toI32(this.input[p - 1]) <= 57) {
1899
+ p--
1900
+ }
1901
+ if (p === end || p === this.tagStart || toI32(this.input[p - 1]) !== 32) {
1902
+ return -1
1903
+ }
1904
+ // Read without touching `text.failed`, which belongs to the program's own decoding.
1905
+ const failed: boolean = this.text.failed
1906
+ this.text.failed = false
1907
+ const rows: i64 = this.text.int(this.input, p, end - p)
1908
+ const overflowed: boolean = this.text.failed
1909
+ this.text.failed = failed
1910
+ return overflowed ? -1 : rows
1911
+ }
1912
+ }
1913
+
1914
+ // --- The pool ------------------------------------------------------------------
1915
+
1916
+ /** `PgPool.next(slot)`: the request `ticket` names will get no answer, because its connection failed first. */
1917
+ export const PG_LOST: i32 = 9
1918
+ /** `PgPool.dispatch()` and `PgPool.wanted()`: no slot. */
1919
+ export const PG_POOL_NONE: i32 = -1
1920
+ /** `PgPool.request()`: the queue is full, and the request was not taken. */
1921
+ export const PG_POOL_FULL: i32 = -2
1922
+
1923
+ /** The most slots a pool may have. */
1924
+ const PG_POOL_MAX_SLOTS: i32 = 1024
1925
+ /** The longest queue a pool may have. */
1926
+ const PG_POOL_MAX_QUEUE: i32 = 1048576
1927
+ /** The most groups a pooled connection may have in flight, which sizes each slot's ring. */
1928
+ const PG_POOL_MAX_IN_FLIGHT: i32 = 1024
1929
+ /** The most statements a pool may register. */
1930
+ const PG_POOL_MAX_STATEMENTS: i32 = 256
1931
+
1932
+ /** No connection: one is built the next time a request needs it. */
1933
+ const PG_SLOT_IDLE: i32 = 0
1934
+ /** A request needs a connection here: the carrier is to make a socket. */
1935
+ const PG_SLOT_WANTED: i32 = 1
1936
+ /** The connection is made and authenticating. */
1937
+ const PG_SLOT_STARTING: i32 = 2
1938
+ const PG_SLOT_READY: i32 = 3
1939
+ /** The connection failed: `next` is reporting the requests it took with it. */
1940
+ const PG_SLOT_DRAINING: i32 = 4
1941
+
1942
+ /** One of a pool's connections, and the groups it has in flight. */
1943
+ class PgPoolSlot {
1944
+ state: i32 = 0
1945
+ conn: PgConnection | null = null
1946
+ /** How many registered statements this connection has queued, and how many the server has described. */
1947
+ queued: i32 = 0
1948
+ described: i32 = 0
1949
+ /** The groups in flight, oldest first, in a ring: each one's ticket, and how many of the pool's own prepares lead it. */
1950
+ readonly tickets: i32[]
1951
+ readonly leads: i32[]
1952
+ head: i32 = 0
1953
+ count: i32 = 0
1954
+
1955
+ constructor(ring: i32) {
1956
+ this.tickets = new Array<i32>(ring)
1957
+ this.leads = new Array<i32>(ring)
1958
+ }
1959
+
1960
+ push(ticket: i32, leads: i32): void {
1961
+ const at: i32 = (this.head + this.count) % toI32(this.tickets.length)
1962
+ this.tickets[at] = ticket
1963
+ this.leads[at] = leads
1964
+ this.count += 1
1965
+ }
1966
+
1967
+ pop(): void {
1968
+ this.head = (this.head + 1) % toI32(this.tickets.length)
1969
+ this.count -= 1
1970
+ }
1971
+ }
1972
+
1973
+ /**
1974
+ * A pool of connections to one server, sans-IO like `PgConnection`: it makes
1975
+ * no socket and reads no clock, and says which slot needs a socket and when.
1976
+ * Its size is a cap, and no connection is made until a request needs one and
1977
+ * no ready connection has room.
1978
+ *
1979
+ * A request is a ticket from `request()`, queued in FIFO order. `dispatch()`
1980
+ * hands the queue's head to the ready connection with the fewest groups in
1981
+ * flight, never past `maxInFlight` on one; the program builds the request's
1982
+ * group on `connection(slot)` and ends it with `send()`, not `sync()`.
1983
+ * `next(slot)` then reads that connection's events, `ticket` naming the
1984
+ * request each is about. A connection that fails reports `PG_LOST` for each
1985
+ * request it took with it, then `PG_FAILED`, and is built again on its next
1986
+ * use. Statements given to `register` are prepared on each connection in the
1987
+ * same group as its first request, so a request may bind them at once.
1988
+ */
1989
+ export class PgPool {
1990
+ /** The request the last `dispatch()`, or event from `next(slot)`, is about; -1 for none. */
1991
+ ticket: i32 = -1
1992
+ /** The number of slots, the most connections the pool makes. */
1993
+ readonly size: i32
1994
+
1995
+ private readonly config: PgConfig
1996
+ private readonly password: PgPassword
1997
+ private readonly slots: PgPoolSlot[]
1998
+ private readonly queue: i32[]
1999
+ private queueHead: i32 = 0
2000
+ private queueCount: i32 = 0
2001
+ private nextTicket: i32 = 0
2002
+ /** The slot whose request is being built, between `dispatch()` and `send()`. */
2003
+ private building: i32 = -1
2004
+ private started: boolean = false
2005
+ private readonly names: string[]
2006
+ private readonly sqls: string[]
2007
+ private readonly types: i32[][]
2008
+
2009
+ /**
2010
+ * A pool of up to `size` connections (1 to 1024) and up to `queueSize`
2011
+ * waiting requests (1 to 2^20). `password` is copied and kept until
2012
+ * `dispose()`, since a connection is made again after a failure. Panics as
2013
+ * `PgConnection` does on `config`, and on a `maxInFlight` over 1024.
2014
+ */
2015
+ constructor(config: PgConfig, password: Secret<u8[]>, size: i32, queueSize: i32) {
2016
+ pgCheckConfig(config)
2017
+ if (size < 1 || size > PG_POOL_MAX_SLOTS) {
2018
+ panic("nish/net/postgres: a PgPool size out of range")
2019
+ }
2020
+ if (queueSize < 1 || queueSize > PG_POOL_MAX_QUEUE) {
2021
+ panic("nish/net/postgres: a PgPool queueSize out of range")
2022
+ }
2023
+ if (config.maxInFlight > PG_POOL_MAX_IN_FLIGHT) {
2024
+ panic("nish/net/postgres: a PgPool connection's maxInFlight is over 1024")
2025
+ }
2026
+ this.config = config
2027
+ this.password = new PgPassword(password)
2028
+ this.size = size
2029
+ this.slots = []
2030
+ for (let i: i32 = 0; i < size; i++) {
2031
+ this.slots.push(new PgPoolSlot(config.maxInFlight))
2032
+ }
2033
+ this.queue = new Array<i32>(queueSize)
2034
+ this.names = []
2035
+ this.sqls = []
2036
+ this.types = []
2037
+ }
2038
+
2039
+ /**
2040
+ * Registers a statement for every connection to prepare before its first
2041
+ * request. False when a connection has been made already, `name` or `sql`
2042
+ * holds a NUL, or 256 are registered.
2043
+ */
2044
+ register(name: string, sql: string, paramTypes: i32[]): boolean {
2045
+ if (
2046
+ this.started ||
2047
+ pgHasNul(name) ||
2048
+ pgHasNul(sql) ||
2049
+ toI32(this.names.length) === PG_POOL_MAX_STATEMENTS
2050
+ ) {
2051
+ return false
2052
+ }
2053
+ const types: i32[] = []
2054
+ for (const oid of paramTypes) {
2055
+ types.push(oid)
2056
+ }
2057
+ this.names.push(name)
2058
+ this.sqls.push(sql)
2059
+ this.types.push(types)
2060
+ return true
2061
+ }
2062
+
2063
+ /** Queues a request and answers its ticket, or `PG_POOL_FULL` when `queueSize` are waiting. */
2064
+ request(): i32 {
2065
+ const room: i32 = toI32(this.queue.length)
2066
+ if (this.queueCount === room) {
2067
+ return PG_POOL_FULL
2068
+ }
2069
+ const ticket: i32 = this.nextTicket
2070
+ this.nextTicket = ticket === 2147483647 ? 0 : ticket + 1
2071
+ this.queue[(this.queueHead + this.queueCount) % room] = ticket
2072
+ this.queueCount += 1
2073
+ return ticket
2074
+ }
2075
+
2076
+ /** The number of requests waiting for a connection. */
2077
+ waiting(): i32 {
2078
+ return this.queueCount
2079
+ }
2080
+
2081
+ /**
2082
+ * Hands the oldest waiting request to a ready connection with room, and
2083
+ * answers its slot, `ticket` set to the request: build its group on
2084
+ * `connection(slot)`, then `send()`. `PG_POOL_NONE` when nothing waits, a
2085
+ * request is still being built, or no ready connection has room; then a
2086
+ * connection may be wanted (`wanted()`): one more for every `maxInFlight`
2087
+ * waiting requests that the connections already starting cannot take. A
2088
+ * request handed out is the program's to end with `send()`: until then no
2089
+ * other is handed out.
2090
+ */
2091
+ dispatch(): i32 {
2092
+ if (this.building >= 0 || this.queueCount === 0) {
2093
+ return PG_POOL_NONE
2094
+ }
2095
+ const cap: i32 = this.config.maxInFlight
2096
+ let best: i32 = -1
2097
+ let load: i32 = 0
2098
+ let starting: i32 = 0
2099
+ let idle: i32 = -1
2100
+ for (let s: i32 = 0; s < this.size; s++) {
2101
+ const slot: PgPoolSlot = this.slots[s]
2102
+ const conn: PgConnection | null = slot.conn
2103
+ if (slot.state === PG_SLOT_READY && conn !== null) {
2104
+ if (conn.inFlight < cap && (best < 0 || conn.inFlight < load)) {
2105
+ best = s
2106
+ load = conn.inFlight
2107
+ }
2108
+ } else if (slot.state === PG_SLOT_WANTED || slot.state === PG_SLOT_STARTING) {
2109
+ starting += 1
2110
+ } else if (slot.state === PG_SLOT_IDLE && idle < 0) {
2111
+ idle = s
2112
+ }
2113
+ }
2114
+ if (best < 0) {
2115
+ if (idle >= 0 && toI64(this.queueCount) > toI64(starting) * toI64(cap)) {
2116
+ this.slots[idle].state = PG_SLOT_WANTED
2117
+ }
2118
+ return PG_POOL_NONE
2119
+ }
2120
+ const ticket: i32 = this.queue[this.queueHead]
2121
+ this.queueHead = (this.queueHead + 1) % toI32(this.queue.length)
2122
+ this.queueCount -= 1
2123
+ const slot: PgPoolSlot = this.slots[best]
2124
+ const conn: PgConnection = this.connection(best)
2125
+ let leads: i32 = 0
2126
+ while (slot.queued < toI32(this.names.length)) {
2127
+ const k: i32 = slot.queued
2128
+ if (!conn.prepare(this.names[k], this.sqls[k], this.types[k])) {
2129
+ break
2130
+ }
2131
+ slot.queued += 1
2132
+ leads += 1
2133
+ }
2134
+ slot.push(ticket, leads)
2135
+ this.building = best
2136
+ this.ticket = ticket
2137
+ return best
2138
+ }
2139
+
2140
+ /**
2141
+ * Ends the group of the request `dispatch()` handed out. False when there
2142
+ * is none; when a bind is still open, so finish it and call again; and
2143
+ * when no message of the group was written, so the request is dropped and
2144
+ * gets no events (`PG_LOST` if its connection has failed).
2145
+ */
2146
+ send(): boolean {
2147
+ const s: i32 = this.building
2148
+ if (s < 0) {
2149
+ return false
2150
+ }
2151
+ const slot: PgPoolSlot = this.slots[s]
2152
+ const conn: PgConnection = this.connection(s)
2153
+ if (conn.sync()) {
2154
+ this.building = -1
2155
+ return true
2156
+ }
2157
+ if (conn.inFlight < slot.count) {
2158
+ // No message opened the group. A failed connection reports the request
2159
+ // lost with the rest; a live one would never answer it, so it is dropped.
2160
+ if (conn.failure === PG_ERR_NONE) {
2161
+ slot.count -= 1
2162
+ }
2163
+ this.building = -1
2164
+ }
2165
+ return false
2166
+ }
2167
+
2168
+ /** A slot that needs a socket, or `PG_POOL_NONE`: connect it, then call `connect(slot, nonce)`. */
2169
+ wanted(): i32 {
2170
+ for (let s: i32 = 0; s < this.size; s++) {
2171
+ if (this.slots[s].state === PG_SLOT_WANTED) {
2172
+ return s
2173
+ }
2174
+ }
2175
+ return PG_POOL_NONE
2176
+ }
2177
+
2178
+ /**
2179
+ * Makes the wanted `slot`'s connection, `nonce` as `PgConnection` takes it:
2180
+ * send its output once the socket is connected, and if the socket fails,
2181
+ * call `connection(slot).closed()`. Panics when `slot` is not wanted or the
2182
+ * pool is disposed.
2183
+ */
2184
+ connect(slot: i32, nonce: u8[]): void {
2185
+ if (slot < 0 || slot >= this.size || this.slots[slot].state !== PG_SLOT_WANTED) {
2186
+ panic("nish/net/postgres: PgPool.connect on a slot that wants no connection")
2187
+ }
2188
+ if (this.password.gone) {
2189
+ panic("nish/net/postgres: PgPool.connect after dispose")
2190
+ }
2191
+ const s: PgPoolSlot = this.slots[slot]
2192
+ s.conn = new PgConnection(this.config, this.password.bytes, nonce)
2193
+ s.state = PG_SLOT_STARTING
2194
+ s.queued = 0
2195
+ s.described = 0
2196
+ s.head = 0
2197
+ s.count = 0
2198
+ this.started = true
2199
+ }
2200
+
2201
+ /** The connection in `slot`, its buffers the carrier's to fill and drain; the last one made after it fails. Panics when none was made. */
2202
+ connection(slot: i32): PgConnection {
2203
+ const conn: PgConnection | null = slot < 0 || slot >= this.size ? null : this.slots[slot].conn
2204
+ if (conn === null) {
2205
+ panic("nish/net/postgres: PgPool.connection on a slot with no connection")
2206
+ }
2207
+ return conn
2208
+ }
2209
+
2210
+ /**
2211
+ * The next event from `slot`'s input, as `PgConnection.next()` answers it,
2212
+ * `ticket` naming the request it is about (-1 for `PG_READY`, `PG_FAILED`,
2213
+ * and a notice while no request is in flight). The `PG_DESCRIBED` of a registered
2214
+ * statement is the pool's and is not answered. When the connection fails,
2215
+ * `PG_LOST` comes for each request in flight on it, oldest first, then
2216
+ * `PG_FAILED`: close its socket. A connection that fails before it is ready,
2217
+ * while no other is, takes the oldest waiting request with it, so that a
2218
+ * server that cannot be reached empties the queue rather than holds it.
2219
+ * `PG_NEED_MORE` once `PG_FAILED` was answered, until `connect` builds the
2220
+ * slot again. Panics as `connection(slot)` does.
2221
+ */
2222
+ next(slot: i32): i32 {
2223
+ const conn: PgConnection = this.connection(slot)
2224
+ const s: PgPoolSlot = this.slots[slot]
2225
+ if (s.state === PG_SLOT_IDLE || s.state === PG_SLOT_WANTED) {
2226
+ this.ticket = -1
2227
+ return PG_NEED_MORE
2228
+ }
2229
+ while (s.state === PG_SLOT_STARTING || s.state === PG_SLOT_READY) {
2230
+ this.ticket = s.count > 0 ? s.tickets[s.head] : -1
2231
+ const event: i32 = conn.next()
2232
+ if (event === PG_READY) {
2233
+ s.state = PG_SLOT_READY
2234
+ } else if (event === PG_DESCRIBED && s.count > 0 && s.leads[s.head] > 0) {
2235
+ s.leads[s.head] -= 1
2236
+ s.described += 1
2237
+ continue
2238
+ } else if (event === PG_ERROR && s.count > 0 && s.leads[s.head] > 0) {
2239
+ // The rest of the group's prepares were skipped: the next request prepares them again.
2240
+ s.leads[s.head] = 0
2241
+ s.queued = s.described
2242
+ } else if (event === PG_GROUP_DONE) {
2243
+ s.pop()
2244
+ } else if (event === PG_FAILED) {
2245
+ if (s.state === PG_SLOT_STARTING && this.queueCount > 0 && !this.anyReady()) {
2246
+ s.push(this.queue[this.queueHead], toI32(0))
2247
+ this.queueHead = (this.queueHead + 1) % toI32(this.queue.length)
2248
+ this.queueCount -= 1
2249
+ }
2250
+ if (this.building === slot) {
2251
+ this.building = -1
2252
+ }
2253
+ s.state = PG_SLOT_DRAINING
2254
+ break
2255
+ }
2256
+ return event
2257
+ }
2258
+ if (s.state === PG_SLOT_DRAINING && s.count > 0) {
2259
+ this.ticket = s.tickets[s.head]
2260
+ s.pop()
2261
+ return PG_LOST
2262
+ }
2263
+ s.state = PG_SLOT_IDLE
2264
+ this.ticket = -1
2265
+ return PG_FAILED
2266
+ }
2267
+
2268
+ private anyReady(): boolean {
2269
+ for (const slot of this.slots) {
2270
+ if (slot.state === PG_SLOT_READY) {
2271
+ return true
2272
+ }
2273
+ }
2274
+ return false
2275
+ }
2276
+
2277
+ /** Whether every byte of the pool's copy of the password is zero, as `PgConnection.passwordWiped()` reads its own. */
2278
+ passwordWiped(): boolean {
2279
+ return this.password.wiped()
2280
+ }
2281
+
2282
+ /** Wipes the pool's password and every connection's; `connect` panics after it. */
2283
+ dispose(): void {
2284
+ if (!this.password.gone) {
2285
+ this.password.wipe()
2286
+ }
2287
+ for (const slot of this.slots) {
2288
+ const conn: PgConnection | null = slot.conn
2289
+ if (conn !== null) {
2290
+ conn.dispose()
2291
+ }
2292
+ }
2293
+ }
2294
+ }