@amritk/nish-x86_64-linux 0.28.0 → 0.30.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.
- package/INSTALL.md +8 -5
- package/bin/nish +0 -0
- package/docs/AI.md +114 -24
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/nish.d.ts +127 -0
- package/runtime/nish.h +94 -5
- package/runtime/nish.mjs +18 -4
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +60 -5
- package/runtime/runtime-region.c +231 -0
- package/runtime/runtime-sandbox.c +43 -34
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +838 -792
- package/scripts/build.sh +55 -12
- package/std/README.md +12 -3
- package/std/collections.ts +5 -5
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-typed.ts +34 -0
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +54 -12
- package/std/net/http1-client.ts +270 -41
- package/std/net/http1-server.ts +145 -32
- package/std/net/http1.ts +274 -51
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres-carrier.ts +985 -0
- package/std/net/postgres.ts +297 -4
- package/std/net/prefork.ts +344 -20
- package/std/net/upstream.ts +759 -0
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
package/std/net/postgres.ts
CHANGED
|
@@ -123,9 +123,12 @@
|
|
|
123
123
|
* `ticket` naming the request. Statements given to `register` are prepared on
|
|
124
124
|
* each connection ahead of its first request. A connection that fails reports
|
|
125
125
|
* `PG_LOST` for each request it took, then `PG_FAILED`; the program closes the
|
|
126
|
-
* socket and the slot is built again on its next use.
|
|
127
|
-
*
|
|
128
|
-
*
|
|
126
|
+
* socket and the slot is built again on its next use.
|
|
127
|
+
*
|
|
128
|
+
* **Carrier.** `PgCarrier`, in `nish/net/postgres-carrier`, moves a pool's
|
|
129
|
+
* bytes on an `HttpHandler`'s loop, so a route handler queries without
|
|
130
|
+
* blocking its worker. It is a module of its own because it reads sockets,
|
|
131
|
+
* the clock and the entropy source, and this one reaches none of them.
|
|
129
132
|
*
|
|
130
133
|
* **Batching.** `PgBatcher` turns many reads of one row shape by key — a user
|
|
131
134
|
* by id, an article by slug — into one execution of a statement over
|
|
@@ -162,6 +165,7 @@ import { timingSafeEqual } from "nish/crypto/ct"
|
|
|
162
165
|
import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
|
|
163
166
|
import { Sha256 } from "nish/crypto/sha256"
|
|
164
167
|
import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
|
|
168
|
+
import { urlDecode } from "nish/net/url"
|
|
165
169
|
|
|
166
170
|
/** `next()` needs more input before it can say anything. */
|
|
167
171
|
export const PG_NEED_MORE: i32 = 0
|
|
@@ -3131,8 +3135,9 @@ export class PgBatcher {
|
|
|
3131
3135
|
refused: i32 = 0
|
|
3132
3136
|
/** The number of `row`, `end` and `fail` calls on a spent handle, each ignored, stopping at 2^31 - 1. */
|
|
3133
3137
|
stray: i32 = 0
|
|
3138
|
+
/** The most requests one execution takes, from `PgBatchConfig.maxRequests`. */
|
|
3139
|
+
readonly maxRequests: i32
|
|
3134
3140
|
private readonly keyType: i32
|
|
3135
|
-
private readonly maxRequests: i32
|
|
3136
3141
|
private readonly maxBytes: i32
|
|
3137
3142
|
private readonly binary: boolean
|
|
3138
3143
|
private storeEnd: i32 = 0
|
|
@@ -3210,6 +3215,24 @@ export class PgBatcher {
|
|
|
3210
3215
|
return this.count
|
|
3211
3216
|
}
|
|
3212
3217
|
|
|
3218
|
+
/**
|
|
3219
|
+
* Drops the oldest waiting request and answers its tag, or -1 when none
|
|
3220
|
+
* waits: for a program that cannot reach the server to fail requests
|
|
3221
|
+
* rather than hold them.
|
|
3222
|
+
*/
|
|
3223
|
+
shed(): i32 {
|
|
3224
|
+
if (this.count === 0) {
|
|
3225
|
+
return -1
|
|
3226
|
+
}
|
|
3227
|
+
const tag: i32 = this.qTags[this.head]
|
|
3228
|
+
this.head = (this.head + 1) % toI32(this.qTags.length)
|
|
3229
|
+
this.count -= 1
|
|
3230
|
+
if (this.count === 0) {
|
|
3231
|
+
this.storeEnd = 0
|
|
3232
|
+
}
|
|
3233
|
+
return tag
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3213
3236
|
/** Queues a request for the int8 `key`; false when `queueSize` are waiting. Panics on a text batcher. */
|
|
3214
3237
|
addInt(key: i64, tag: i32): boolean {
|
|
3215
3238
|
if (this.keyType !== PG_BATCH_INT) {
|
|
@@ -3958,3 +3981,273 @@ export class PgBatchTurns {
|
|
|
3958
3981
|
}
|
|
3959
3982
|
}
|
|
3960
3983
|
}
|
|
3984
|
+
|
|
3985
|
+
/** `PgTarget.failure`: none; connect as `unix` says. */
|
|
3986
|
+
export const PG_TARGET_OK: i32 = 0
|
|
3987
|
+
/** `PgTarget.failure`: not a `postgres://` or `postgresql://` URL, or a query parameter with no `=`. */
|
|
3988
|
+
export const PG_TARGET_BAD_URL: i32 = 1
|
|
3989
|
+
/** `PgTarget.failure`: a `%` not followed by two hex digits, `%00`, or escapes that decode to bytes that are not UTF-8. */
|
|
3990
|
+
export const PG_TARGET_BAD_ESCAPE: i32 = 2
|
|
3991
|
+
/** `PgTarget.failure`: no host, more than one (a `,`), an unclosed `[` or text after its `]`, a raw `@` past the user information, or a NUL byte. */
|
|
3992
|
+
export const PG_TARGET_BAD_HOST: i32 = 3
|
|
3993
|
+
/** `PgTarget.failure`: a port that is not 1 to 65535 in decimal digits. */
|
|
3994
|
+
export const PG_TARGET_BAD_PORT: i32 = 4
|
|
3995
|
+
/** `PgTarget.failure`: a socket path longer than Linux's `sun_path` holds. */
|
|
3996
|
+
export const PG_TARGET_TOO_LONG: i32 = 5
|
|
3997
|
+
|
|
3998
|
+
/** The port a target names when nothing else does. */
|
|
3999
|
+
const PG_DEFAULT_PORT: i32 = 5432
|
|
4000
|
+
/** The longest socket path, NUL excluded: Linux's 108-byte `sun_path`, less its NUL; an `@` name has no NUL, so its `@` takes that byte. */
|
|
4001
|
+
const PG_SOCKET_PATH_MAX: i32 = 107
|
|
4002
|
+
|
|
4003
|
+
/**
|
|
4004
|
+
* Where a connection goes, from libpq's `host` and `port` or a
|
|
4005
|
+
* `postgres://` URL: `pgTarget` and `pgTargetUrl` make one, reading no
|
|
4006
|
+
* socket and no file. When `failure` is `PG_TARGET_OK`, a `unix` target is
|
|
4007
|
+
* `unixConnect(path)`, and any other is `host` and `port`, for
|
|
4008
|
+
* `netAddress` and `tcpConnect`.
|
|
4009
|
+
*
|
|
4010
|
+
* A host starting with `/` is a socket directory, and one starting with `@`
|
|
4011
|
+
* a Linux abstract name, as in libpq: the socket is
|
|
4012
|
+
* `<host>/.s.PGSQL.<port>`. The host is always the directory, never the
|
|
4013
|
+
* socket file itself. Any other host is TCP.
|
|
4014
|
+
*/
|
|
4015
|
+
export class PgTarget {
|
|
4016
|
+
/** `PG_TARGET_OK`, or the `PG_TARGET_*` reason the input was refused. */
|
|
4017
|
+
failure: i32 = 0
|
|
4018
|
+
/** Whether the target is a Unix socket, at `path`. */
|
|
4019
|
+
unix: boolean = false
|
|
4020
|
+
/** The host as given (a URL's decoded, without an IPv6 address's brackets); for a socket, its directory or `@` name. */
|
|
4021
|
+
host: string = ""
|
|
4022
|
+
/** The port: the TCP port, or the number in the socket's name. */
|
|
4023
|
+
port: i32 = 5432
|
|
4024
|
+
/** The socket's path, `<host>/.s.PGSQL.<port>`, when `unix`; otherwise "". */
|
|
4025
|
+
path: string = ""
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
/** A target refused for `failure`. */
|
|
4029
|
+
const pgTargetFailed = (failure: i32): PgTarget => {
|
|
4030
|
+
const t = new PgTarget()
|
|
4031
|
+
t.failure = failure
|
|
4032
|
+
return t
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
/**
|
|
4036
|
+
* The target `host` and `port` name, as libpq's `host` and `port` do: a host
|
|
4037
|
+
* starting with `/` or `@` is the socket `<host>/.s.PGSQL.<port>`, any other
|
|
4038
|
+
* a TCP host. The host is not percent-decoded. A port outside 1 to 65535, an
|
|
4039
|
+
* empty host, a `,` (more than one host) or a NUL byte is refused, and so is
|
|
4040
|
+
* a socket path over 107 bytes (an `@` name over 107 after its `@`), never
|
|
4041
|
+
* cut short.
|
|
4042
|
+
*/
|
|
4043
|
+
export const pgTarget = (host: string, port: i32): PgTarget => {
|
|
4044
|
+
if (port < 1 || port > 65535) {
|
|
4045
|
+
return pgTargetFailed(PG_TARGET_BAD_PORT)
|
|
4046
|
+
}
|
|
4047
|
+
const n: i32 = toI32(host.length)
|
|
4048
|
+
if (n === 0) {
|
|
4049
|
+
return pgTargetFailed(PG_TARGET_BAD_HOST)
|
|
4050
|
+
}
|
|
4051
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
4052
|
+
const c: i32 = toI32(host.charCodeAt(i))
|
|
4053
|
+
if (c === 0 || c === 44) {
|
|
4054
|
+
return pgTargetFailed(PG_TARGET_BAD_HOST)
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
const t = new PgTarget()
|
|
4058
|
+
t.host = host
|
|
4059
|
+
t.port = port
|
|
4060
|
+
const first: i32 = toI32(host.charCodeAt(0))
|
|
4061
|
+
if (first === 47 || first === 64) {
|
|
4062
|
+
const path: string = `${host}/.s.PGSQL.${port}`
|
|
4063
|
+
// An `@` name's NUL-free bytes after the `@` fill what a path's NUL would.
|
|
4064
|
+
const limit: i32 = first === 64 ? PG_SOCKET_PATH_MAX + 1 : PG_SOCKET_PATH_MAX
|
|
4065
|
+
if (toI32(path.length) > limit) {
|
|
4066
|
+
return pgTargetFailed(PG_TARGET_TOO_LONG)
|
|
4067
|
+
}
|
|
4068
|
+
t.unix = true
|
|
4069
|
+
t.path = path
|
|
4070
|
+
}
|
|
4071
|
+
return t
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
/** `text[start, end)` percent-decoded, or null for a `%` without two hex digits after it, `%00`, or bytes that are not UTF-8. */
|
|
4075
|
+
const pgDecoded = (text: string, start: i32, end: i32): string | null =>
|
|
4076
|
+
urlDecode(text.substring(start, end), 0)
|
|
4077
|
+
|
|
4078
|
+
/** The port `text` spells, 1 to 5 decimal digits naming 1 to 65535, or -1. */
|
|
4079
|
+
const pgPortValue = (text: string): i32 => {
|
|
4080
|
+
const n: i32 = toI32(text.length)
|
|
4081
|
+
if (n === 0 || n > 5) {
|
|
4082
|
+
return -1
|
|
4083
|
+
}
|
|
4084
|
+
let v: i32 = 0
|
|
4085
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
4086
|
+
const c: i32 = toI32(text.charCodeAt(i))
|
|
4087
|
+
if (c < 48 || c > 57) {
|
|
4088
|
+
return -1
|
|
4089
|
+
}
|
|
4090
|
+
v = v * 10 + (c - 48)
|
|
4091
|
+
}
|
|
4092
|
+
if (v < 1 || v > 65535) {
|
|
4093
|
+
return -1
|
|
4094
|
+
}
|
|
4095
|
+
return v
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
/**
|
|
4099
|
+
* The target a `postgres://` or `postgresql://` URL names, as libpq reads
|
|
4100
|
+
* one: `postgres://[user[:password]@][host][:port][/dbname][?key=value&...]`.
|
|
4101
|
+
* The host, the port and every query key and value are percent-decoded, so a
|
|
4102
|
+
* socket directory is written `%2Ftmp` or passed as `?host=/tmp`; a `host` or
|
|
4103
|
+
* `port` in the query overrides the authority's, and other keys are ignored.
|
|
4104
|
+
* The user, password and database are not read here, but a bad escape in them
|
|
4105
|
+
* still refuses the URL. A `%` without two hex digits after it, `%00`, or a
|
|
4106
|
+
* part that decodes to bytes that are not UTF-8 is `PG_TARGET_BAD_ESCAPE`.
|
|
4107
|
+
* The user information ends at the first `@`, as in libpq; a raw `@` after a
|
|
4108
|
+
* `?` or after that first `@` in the authority, or a raw NUL byte anywhere,
|
|
4109
|
+
* is `PG_TARGET_BAD_HOST`, as is a URL with no host anywhere, where libpq
|
|
4110
|
+
* would use its default socket directory. An empty port or query value is
|
|
4111
|
+
* refused, where libpq would take it as unset.
|
|
4112
|
+
* The decoded host is then resolved as `pgTarget` resolves one.
|
|
4113
|
+
*/
|
|
4114
|
+
export const pgTargetUrl = (url: string): PgTarget => {
|
|
4115
|
+
let at: i32 = 0
|
|
4116
|
+
if (url.startsWith("postgresql://")) {
|
|
4117
|
+
at = 13
|
|
4118
|
+
} else if (url.startsWith("postgres://")) {
|
|
4119
|
+
at = 11
|
|
4120
|
+
} else {
|
|
4121
|
+
return pgTargetFailed(PG_TARGET_BAD_URL)
|
|
4122
|
+
}
|
|
4123
|
+
const n: i32 = toI32(url.length)
|
|
4124
|
+
// The authority runs to the first `/` or `?`; the path to the `?`.
|
|
4125
|
+
let authorityEnd: i32 = at
|
|
4126
|
+
while (authorityEnd < n) {
|
|
4127
|
+
const c: i32 = toI32(url.charCodeAt(authorityEnd))
|
|
4128
|
+
if (c === 47 || c === 63) {
|
|
4129
|
+
break
|
|
4130
|
+
}
|
|
4131
|
+
authorityEnd += 1
|
|
4132
|
+
}
|
|
4133
|
+
let queryStart: i32 = authorityEnd
|
|
4134
|
+
while (queryStart < n && toI32(url.charCodeAt(queryStart)) !== 63) {
|
|
4135
|
+
queryStart += 1
|
|
4136
|
+
}
|
|
4137
|
+
// A raw NUL byte anywhere is refused: no part may carry one past here.
|
|
4138
|
+
for (let i: i32 = at; i < n; i++) {
|
|
4139
|
+
if (toI32(url.charCodeAt(i)) === 0) {
|
|
4140
|
+
return pgTargetFailed(PG_TARGET_BAD_HOST)
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
// libpq ends the user information at the first `@` before a `/`, a `?`
|
|
4144
|
+
// included. One after a `?`, or a second in the host and port, reads
|
|
4145
|
+
// differently by other parsers, so either is refused. The loops below also
|
|
4146
|
+
// test `>= 0` and `< n`, always true as `authorityEnd <= n`, so the prover
|
|
4147
|
+
// drops each access's bounds check.
|
|
4148
|
+
let firstAt: i32 = at
|
|
4149
|
+
while (firstAt >= 0 && firstAt < n) {
|
|
4150
|
+
const c: i32 = toI32(url.charCodeAt(firstAt))
|
|
4151
|
+
if (c === 64 || c === 47) {
|
|
4152
|
+
break
|
|
4153
|
+
}
|
|
4154
|
+
firstAt += 1
|
|
4155
|
+
}
|
|
4156
|
+
let hostStart: i32 = at
|
|
4157
|
+
if (firstAt < n && toI32(url.charCodeAt(firstAt)) === 64) {
|
|
4158
|
+
if (firstAt > authorityEnd) {
|
|
4159
|
+
return pgTargetFailed(PG_TARGET_BAD_HOST)
|
|
4160
|
+
}
|
|
4161
|
+
hostStart = firstAt + 1
|
|
4162
|
+
}
|
|
4163
|
+
for (let i: i32 = hostStart; i >= 0 && i < authorityEnd && i < n; i++) {
|
|
4164
|
+
if (toI32(url.charCodeAt(i)) === 64) {
|
|
4165
|
+
return pgTargetFailed(PG_TARGET_BAD_HOST)
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
if (hostStart > at && pgDecoded(url, at, hostStart - 1) === null) {
|
|
4169
|
+
return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
|
|
4170
|
+
}
|
|
4171
|
+
if (authorityEnd < queryStart && pgDecoded(url, authorityEnd, queryStart) === null) {
|
|
4172
|
+
return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
|
|
4173
|
+
}
|
|
4174
|
+
// The host ends at a `:` before the port, after an IPv6 address's `]`.
|
|
4175
|
+
let hostEnd: i32 = authorityEnd
|
|
4176
|
+
let portStart: i32 = -1
|
|
4177
|
+
let bracketed: boolean = false
|
|
4178
|
+
if (hostStart < authorityEnd && toI32(url.charCodeAt(hostStart)) === 91) {
|
|
4179
|
+
let close: i32 = hostStart + 1
|
|
4180
|
+
while (close >= 0 && close < authorityEnd && close < n && toI32(url.charCodeAt(close)) !== 93) {
|
|
4181
|
+
close += 1
|
|
4182
|
+
}
|
|
4183
|
+
if (close === authorityEnd) {
|
|
4184
|
+
return pgTargetFailed(PG_TARGET_BAD_HOST)
|
|
4185
|
+
}
|
|
4186
|
+
bracketed = true
|
|
4187
|
+
hostEnd = close + 1
|
|
4188
|
+
if (hostEnd < authorityEnd) {
|
|
4189
|
+
if (toI32(url.charCodeAt(hostEnd)) !== 58) {
|
|
4190
|
+
return pgTargetFailed(PG_TARGET_BAD_HOST)
|
|
4191
|
+
}
|
|
4192
|
+
portStart = hostEnd + 1
|
|
4193
|
+
}
|
|
4194
|
+
} else {
|
|
4195
|
+
for (let i: i32 = hostStart; i >= 0 && i < authorityEnd && i < n; i++) {
|
|
4196
|
+
if (toI32(url.charCodeAt(i)) === 58) {
|
|
4197
|
+
hostEnd = i
|
|
4198
|
+
portStart = i + 1
|
|
4199
|
+
break
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
const decodedHost: string | null = bracketed
|
|
4204
|
+
? pgDecoded(url, hostStart + 1, hostEnd - 1)
|
|
4205
|
+
: pgDecoded(url, hostStart, hostEnd)
|
|
4206
|
+
if (decodedHost === null) {
|
|
4207
|
+
return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
|
|
4208
|
+
}
|
|
4209
|
+
let host: string = decodedHost
|
|
4210
|
+
let port: i32 = PG_DEFAULT_PORT
|
|
4211
|
+
if (portStart >= 0) {
|
|
4212
|
+
const decodedPort: string | null = pgDecoded(url, portStart, authorityEnd)
|
|
4213
|
+
if (decodedPort === null) {
|
|
4214
|
+
return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
|
|
4215
|
+
}
|
|
4216
|
+
port = pgPortValue(decodedPort)
|
|
4217
|
+
if (port < 0) {
|
|
4218
|
+
return pgTargetFailed(PG_TARGET_BAD_PORT)
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
// The query: `key=value` pairs split at `&`.
|
|
4222
|
+
let i: i32 = queryStart + 1
|
|
4223
|
+
while (i >= 0 && i < n) {
|
|
4224
|
+
let end: i32 = i
|
|
4225
|
+
let eq: i32 = -1
|
|
4226
|
+
while (end < n && toI32(url.charCodeAt(end)) !== 38) {
|
|
4227
|
+
if (eq < 0 && toI32(url.charCodeAt(end)) === 61) {
|
|
4228
|
+
eq = end
|
|
4229
|
+
}
|
|
4230
|
+
end += 1
|
|
4231
|
+
}
|
|
4232
|
+
if (end > i) {
|
|
4233
|
+
if (eq < 0) {
|
|
4234
|
+
return pgTargetFailed(PG_TARGET_BAD_URL)
|
|
4235
|
+
}
|
|
4236
|
+
const key: string | null = pgDecoded(url, i, eq)
|
|
4237
|
+
const value: string | null = pgDecoded(url, eq + 1, end)
|
|
4238
|
+
if (key === null || value === null) {
|
|
4239
|
+
return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
|
|
4240
|
+
}
|
|
4241
|
+
if (key === "host") {
|
|
4242
|
+
host = value
|
|
4243
|
+
} else if (key === "port") {
|
|
4244
|
+
port = pgPortValue(value)
|
|
4245
|
+
if (port < 0) {
|
|
4246
|
+
return pgTargetFailed(PG_TARGET_BAD_PORT)
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
i = end + 1
|
|
4251
|
+
}
|
|
4252
|
+
return pgTarget(host, port)
|
|
4253
|
+
}
|