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