@amritk/nish-x86_64-linux 0.28.0 → 0.29.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 +99 -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 +72 -3
- 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-sandbox.c +43 -34
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +827 -791
- package/scripts/build.sh +42 -12
- package/std/README.md +10 -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-server.ts +77 -23
- package/std/net/http1.ts +24 -4
- package/std/net/outbound.ts +409 -0
- package/std/net/prefork.ts +344 -20
- 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
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/outbound` — the policy for a program that fetches URLs it did
|
|
3
|
+
* not choose: a webhook sender, a link previewer, an importer that takes a
|
|
4
|
+
* URL from a form. Without one, such a program is a proxy into its own
|
|
5
|
+
* network for whoever supplies the URL (server-side request forgery): a URL
|
|
6
|
+
* naming `127.0.0.1`, `169.254.169.254` or a name that resolves to either
|
|
7
|
+
* reaches services that trust the program's address.
|
|
8
|
+
*
|
|
9
|
+
* import { OutboundPolicy } from "nish/net/outbound";
|
|
10
|
+
* import { HttpOptions } from "nish/net/http-client";
|
|
11
|
+
*
|
|
12
|
+
* const options = new HttpOptions();
|
|
13
|
+
* options.outbound = new OutboundPolicy();
|
|
14
|
+
*
|
|
15
|
+
* **The check is on the address, after the lookup.** A name is never judged
|
|
16
|
+
* by its text: `nish/net/http-client` resolves it, drops every address
|
|
17
|
+
* `outboundAddressAllowed` refuses, and connects only to the bytes it kept,
|
|
18
|
+
* with no second lookup, so a name whose answer changes between a check and
|
|
19
|
+
* a connect (DNS rebinding) cannot move the connection. Every redirect hop is
|
|
20
|
+
* dialled the same way, so a public URL that redirects inward is refused at
|
|
21
|
+
* the hop.
|
|
22
|
+
*
|
|
23
|
+
* **Public** is the IANA special-purpose registries read strictly
|
|
24
|
+
* (RFC 6890, and the registries at iana.org/assignments/iana-ipv4-special-registry
|
|
25
|
+
* and iana-ipv6-special-registry). An IPv4 address is public unless it is in
|
|
26
|
+
* `0/8`, `10/8`, `100.64/10`, `127/8`, `169.254/16`, `172.16/12`,
|
|
27
|
+
* `192.0.0/24`, `192.0.2/24`, `192.88.99/24`, `192.168/16`, `198.18/15`,
|
|
28
|
+
* `198.51.100/24`, `203.0.113/24`, `224/4` or `240/4` (the broadcast
|
|
29
|
+
* address included), or is `168.63.129.16`, a cloud host's metadata and DNS
|
|
30
|
+
* service. An IPv6 address is public only in `2000::/3`, and then not in
|
|
31
|
+
* `2001::/23` (Teredo included), `2001:db8::/32` or `3fff::/20`; `2002::/16`
|
|
32
|
+
* (6to4) and `64:ff9b::/96` (NAT64) are judged by the IPv4 address they carry,
|
|
33
|
+
* as is `::ffff:0:0/96`, the form every IPv4 address takes in `nish:net`.
|
|
34
|
+
* Everything else, `::1`, `fc00::/7` and `fe80::/10` among it, is refused.
|
|
35
|
+
*
|
|
36
|
+
* `permit` lets a trusted prefix through anyway (an internal API the program
|
|
37
|
+
* is meant to reach), and `allowHost` narrows the names a URL may carry. The
|
|
38
|
+
* policy also caps a fetch's redirects, total time and body, and the client
|
|
39
|
+
* uses the stricter of these and its own options.
|
|
40
|
+
*
|
|
41
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
42
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
43
|
+
*
|
|
44
|
+
* Written from RFC 6890, RFC 4291, RFC 6052, RFC 3056, RFC 4380 and the IANA
|
|
45
|
+
* special-purpose address registries, not ported from another implementation.
|
|
46
|
+
*/
|
|
47
|
+
import { copyBytes } from "nish/bytes"
|
|
48
|
+
import { DNS_ADDRESS_BYTES } from "nish/net/dns-message"
|
|
49
|
+
import { dnsParseAddress } from "nish/net/dns-config"
|
|
50
|
+
|
|
51
|
+
/** The bytes of one `permit` entry: sixteen of address, then the prefix length in bits. */
|
|
52
|
+
const OUTBOUND_PERMIT_BYTES: i32 = 17
|
|
53
|
+
|
|
54
|
+
/** Where an IPv4 address starts in its sixteen-byte mapped form. */
|
|
55
|
+
const OUTBOUND_V4_AT: i32 = 12
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* What a client that fetches untrusted URLs may reach. A new policy refuses
|
|
59
|
+
* every address that is not public, allows any host name, and caps a fetch
|
|
60
|
+
* at 5 redirects, 30 seconds and an 8 MiB body.
|
|
61
|
+
*/
|
|
62
|
+
export class OutboundPolicy {
|
|
63
|
+
// Declared widest first, so no byte of a policy is padding.
|
|
64
|
+
/** The names a URL may carry, lower-cased; empty for any. See `allowHost`. */
|
|
65
|
+
allowHosts: string[]
|
|
66
|
+
/** `OUTBOUND_PERMIT_BYTES` per prefix `permit` let through. */
|
|
67
|
+
permitted: u8[]
|
|
68
|
+
/** A fetch's body is cut off past this, or past the client's own cap if lower. */
|
|
69
|
+
maxBodyBytes: i64 = 8388608
|
|
70
|
+
/** A whole fetch ends after this, or the client's own budget if lower; 0 or less for the client's alone. */
|
|
71
|
+
totalTimeoutMs: i32 = 30000
|
|
72
|
+
/** At most this many redirects, or the client's own limit if lower. */
|
|
73
|
+
maxRedirects: i32 = 5
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
this.allowHosts = []
|
|
77
|
+
this.permitted = []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Adds `name` to the hosts a URL may name; once any is added, a URL (or a
|
|
82
|
+
* redirect) naming another is refused before it is looked up. Case and one
|
|
83
|
+
* trailing dot do not matter. A leading dot (`.example.com`) allows the
|
|
84
|
+
* name and every name under it. An address literal matches only itself,
|
|
85
|
+
* as the URL writes it (`::1` or `[::1]`, `127.0.0.1`), never as a suffix.
|
|
86
|
+
*/
|
|
87
|
+
allowHost(name: string): void {
|
|
88
|
+
let entry: string = outboundLower(name)
|
|
89
|
+
if (entry.startsWith("[") && entry.endsWith("]")) {
|
|
90
|
+
entry = entry.substring(1, toI32(entry.length) - 1)
|
|
91
|
+
}
|
|
92
|
+
this.allowHosts.push(outboundTrimDot(entry))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Lets the addresses in `cidr` through though they are not public:
|
|
97
|
+
* `10.1.2.0/24`, `fd00:1::/64`, or one address alone. False, with nothing
|
|
98
|
+
* added, when `cidr` is not an address and a prefix length that fits it.
|
|
99
|
+
*/
|
|
100
|
+
permit(cidr: string): boolean {
|
|
101
|
+
const n: i32 = toI32(cidr.length)
|
|
102
|
+
const slash: i32 = toI32(cidr.indexOf("/"))
|
|
103
|
+
const end: i32 = slash >= 0 ? slash : n
|
|
104
|
+
const text: u8[] = new Array<u8>(end)
|
|
105
|
+
let v4: boolean = true
|
|
106
|
+
for (let i: i32 = 0; i < toI32(text.length) && i < n; i += 1) {
|
|
107
|
+
const c: i32 = toI32(cidr.charCodeAt(i))
|
|
108
|
+
if (c > 127) {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
v4 = v4 && c !== 58
|
|
112
|
+
text[i] = toU8(c)
|
|
113
|
+
}
|
|
114
|
+
const address: u8[] = new Array<u8>(DNS_ADDRESS_BYTES)
|
|
115
|
+
if (end === 0 || !dnsParseAddress(text, 0, end, address, 0, 0)) {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
const width: i32 = v4 ? 32 : 128
|
|
119
|
+
let bits: i32 = width
|
|
120
|
+
if (slash >= 0) {
|
|
121
|
+
bits = outboundDecimal(cidr, slash + 1, n)
|
|
122
|
+
if (bits < 0 || bits > width) {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (let i: i32 = 0; i < 16 && i < toI32(address.length); i += 1) {
|
|
127
|
+
this.permitted.push(address[i])
|
|
128
|
+
}
|
|
129
|
+
this.permitted.push(toU8(v4 ? bits + 96 : bits))
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Whether the sixteen address bytes at `addr[off]` are public, as the module
|
|
136
|
+
* comment defines it. Panics when sixteen bytes do not fit there.
|
|
137
|
+
*/
|
|
138
|
+
export const outboundAddressPublic = (addr: u8[], off: i32): boolean => {
|
|
139
|
+
if (off < 0 || off > toI32(addr.length) - 16) {
|
|
140
|
+
panic("outboundAddressPublic: sixteen bytes do not fit at off")
|
|
141
|
+
}
|
|
142
|
+
const carried: i32 = outboundCarriedV4(addr, off)
|
|
143
|
+
if (carried >= 0) {
|
|
144
|
+
return outboundV4Public(addr, carried)
|
|
145
|
+
}
|
|
146
|
+
const b0: i32 = toI32(addr[off])
|
|
147
|
+
const b1: i32 = toI32(addr[off + 1])
|
|
148
|
+
const b2: i32 = toI32(addr[off + 2])
|
|
149
|
+
const b3: i32 = toI32(addr[off + 3])
|
|
150
|
+
if ((b0 & 0xe0) !== 0x20) {
|
|
151
|
+
// Outside 2000::/3: loopback, unspecified, unique local, link-local, multicast, the rest.
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
if (b0 === 0x20 && b1 === 0x01 && (b2 & 0xfe) === 0) {
|
|
155
|
+
// 2001::/23, IETF protocol assignments, Teredo's 2001::/32 among them.
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
if (b0 === 0x20 && b1 === 0x01 && b2 === 0x0d && b3 === 0xb8) {
|
|
159
|
+
// 2001:db8::/32, documentation.
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
// 3fff::/20, documentation (RFC 9637).
|
|
163
|
+
return !(b0 === 0x3f && b1 === 0xff && (b2 & 0xf0) === 0)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Whether `policy` lets a connection go to the sixteen address bytes at `addr[off]`. */
|
|
167
|
+
export const outboundAddressAllowed = (policy: OutboundPolicy, addr: u8[], off: i32): boolean => {
|
|
168
|
+
if (outboundPermitted(policy, addr, off)) {
|
|
169
|
+
return true
|
|
170
|
+
}
|
|
171
|
+
const carried: i32 = outboundCarriedV4(addr, off)
|
|
172
|
+
if (carried < 0) {
|
|
173
|
+
return outboundAddressPublic(addr, off)
|
|
174
|
+
}
|
|
175
|
+
if (!outboundMapped(addr, off) && toI32(policy.permitted.length) > 0) {
|
|
176
|
+
// A permit names an IPv4 prefix in its ::ffff: form, so try the address a NAT64 or 6to4 one carries in that form too.
|
|
177
|
+
const mapped: u8[] = new Array<u8>(16)
|
|
178
|
+
mapped[10] = toU8(255)
|
|
179
|
+
mapped[11] = toU8(255)
|
|
180
|
+
copyBytes(mapped, OUTBOUND_V4_AT, addr, carried, 4)
|
|
181
|
+
if (outboundPermitted(policy, mapped, 0)) {
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return outboundV4Public(addr, carried)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Keeps the addresses of `count` `DNS_ADDRESS_BYTES` records in `addresses`
|
|
190
|
+
* that `policy` allows, moved to the front in their order, and answers how
|
|
191
|
+
* many. Refused records are left behind them, so the caller can name one.
|
|
192
|
+
*/
|
|
193
|
+
export const outboundFilter = (policy: OutboundPolicy, addresses: u8[], count: i32): i32 => {
|
|
194
|
+
if (count < 0 || count * DNS_ADDRESS_BYTES > toI32(addresses.length)) {
|
|
195
|
+
panic("outboundFilter: count records do not fit in addresses")
|
|
196
|
+
}
|
|
197
|
+
let kept: i32 = 0
|
|
198
|
+
for (let i: i32 = 0; i < count; i += 1) {
|
|
199
|
+
if (!outboundAddressAllowed(policy, addresses, i * DNS_ADDRESS_BYTES)) {
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
if (i !== kept) {
|
|
203
|
+
// Swap, so the refused record stays in the list for an error to name.
|
|
204
|
+
for (let k: i32 = 0; k < DNS_ADDRESS_BYTES; k += 1) {
|
|
205
|
+
const held: u8 = addresses[kept * DNS_ADDRESS_BYTES + k]
|
|
206
|
+
addresses[kept * DNS_ADDRESS_BYTES + k] = addresses[i * DNS_ADDRESS_BYTES + k]
|
|
207
|
+
addresses[i * DNS_ADDRESS_BYTES + k] = held
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
kept += 1
|
|
211
|
+
}
|
|
212
|
+
return kept
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Whether `policy` lets a URL name `host`, as the URL parser gives it
|
|
217
|
+
* (lower-cased, an IPv6 literal without its brackets). Any host when the
|
|
218
|
+
* policy names none.
|
|
219
|
+
*/
|
|
220
|
+
export const outboundHostAllowed = (policy: OutboundPolicy, host: string): boolean => {
|
|
221
|
+
if (policy.allowHosts.length === 0) {
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
const name: string = outboundTrimDot(outboundLower(host))
|
|
225
|
+
const literal: boolean = outboundIsLiteral(name)
|
|
226
|
+
for (let i: i32 = 0; i < toI32(policy.allowHosts.length); i += 1) {
|
|
227
|
+
const entry: string = policy.allowHosts[i]
|
|
228
|
+
if (entry === name) {
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
if (
|
|
232
|
+
!literal &&
|
|
233
|
+
entry.startsWith(".") &&
|
|
234
|
+
(name.endsWith(entry) || (toI32(name.length) + 1 === toI32(entry.length) && entry.endsWith(name)))
|
|
235
|
+
) {
|
|
236
|
+
return true
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** The sixteen address bytes at `addr[off]` as text: dotted for an IPv4 address, eight hex groups otherwise. */
|
|
243
|
+
export const outboundAddressText = (addr: u8[], off: i32): string => {
|
|
244
|
+
if (outboundMapped(addr, off)) {
|
|
245
|
+
const at: i32 = off + OUTBOUND_V4_AT
|
|
246
|
+
return `${addr[at]}.${addr[at + 1]}.${addr[at + 2]}.${addr[at + 3]}`
|
|
247
|
+
}
|
|
248
|
+
// Eight groups of at most four digits and seven colons.
|
|
249
|
+
const out: u8[] = new Array<u8>(39)
|
|
250
|
+
let at: i32 = 0
|
|
251
|
+
for (let g: i32 = 0; g < 8; g += 1) {
|
|
252
|
+
const v: i32 = toI32(addr[off + 2 * g]) * 256 + toI32(addr[off + 2 * g + 1])
|
|
253
|
+
if (g > 0 && at < toI32(out.length)) {
|
|
254
|
+
out[at] = toU8(58)
|
|
255
|
+
at += 1
|
|
256
|
+
}
|
|
257
|
+
for (let shift: i32 = 12; shift >= 0; shift -= 4) {
|
|
258
|
+
const d: i32 = (v >> shift) & 15
|
|
259
|
+
if (v >> shift !== 0 || shift === 0) {
|
|
260
|
+
if (at < toI32(out.length)) {
|
|
261
|
+
out[at] = toU8(d < 10 ? 48 + d : 87 + d)
|
|
262
|
+
at += 1
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return String.fromBytes(out, 0, at)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// --- helpers -----------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
/** Whether one of `policy`'s permits covers the sixteen address bytes at `addr[off]`. */
|
|
273
|
+
const outboundPermitted = (policy: OutboundPolicy, addr: u8[], off: i32): boolean => {
|
|
274
|
+
const p: u8[] = policy.permitted
|
|
275
|
+
for (let at: i32 = 0; at + OUTBOUND_PERMIT_BYTES <= toI32(p.length); at += OUTBOUND_PERMIT_BYTES) {
|
|
276
|
+
const bitsAt: i32 = at + 16
|
|
277
|
+
if (bitsAt >= 0 && bitsAt < toI32(p.length) && outboundPrefixMatch(p, at, toI32(p[bitsAt]), addr, off)) {
|
|
278
|
+
return true
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return false
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Whether the sixteen address bytes at `addr[off]` are an IPv4-mapped address, `::ffff:0:0/96`. */
|
|
285
|
+
const outboundMapped = (addr: u8[], off: i32): boolean =>
|
|
286
|
+
outboundZeros(addr, off, 10) && addr[off + 10] === toU8(255) && addr[off + 11] === toU8(255)
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Where the IPv4 address that the sixteen address bytes at `addr[off]` carry
|
|
290
|
+
* starts, or -1 for none: `::ffff:a.b.c.d`, how nish:net writes every IPv4
|
|
291
|
+
* address; `64:ff9b::/96`, the NAT64 well-known prefix (RFC 6052), in its
|
|
292
|
+
* last four bytes; `2002::/16`, 6to4 (RFC 3056), in the four after the prefix.
|
|
293
|
+
*/
|
|
294
|
+
const outboundCarriedV4 = (addr: u8[], off: i32): i32 => {
|
|
295
|
+
if (outboundMapped(addr, off)) {
|
|
296
|
+
return off + OUTBOUND_V4_AT
|
|
297
|
+
}
|
|
298
|
+
const b0: i32 = toI32(addr[off])
|
|
299
|
+
const b1: i32 = toI32(addr[off + 1])
|
|
300
|
+
if (
|
|
301
|
+
b0 === 0 &&
|
|
302
|
+
b1 === 0x64 &&
|
|
303
|
+
addr[off + 2] === toU8(0xff) &&
|
|
304
|
+
addr[off + 3] === toU8(0x9b) &&
|
|
305
|
+
outboundZeros(addr, off + 4, 8)
|
|
306
|
+
) {
|
|
307
|
+
return off + OUTBOUND_V4_AT
|
|
308
|
+
}
|
|
309
|
+
if (b0 === 0x20 && b1 === 0x02) {
|
|
310
|
+
return off + 2
|
|
311
|
+
}
|
|
312
|
+
return -1
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Whether the IPv4 address in the four bytes at `addr[at]` is public. */
|
|
316
|
+
const outboundV4Public = (addr: u8[], at: i32): boolean => {
|
|
317
|
+
const a: i32 = toI32(addr[at])
|
|
318
|
+
const b: i32 = toI32(addr[at + 1])
|
|
319
|
+
const c: i32 = toI32(addr[at + 2])
|
|
320
|
+
const d: i32 = toI32(addr[at + 3])
|
|
321
|
+
return !(
|
|
322
|
+
a === 0 ||
|
|
323
|
+
a === 10 ||
|
|
324
|
+
a === 127 ||
|
|
325
|
+
a >= 224 ||
|
|
326
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
327
|
+
(a === 169 && b === 254) ||
|
|
328
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
329
|
+
(a === 192 && b === 0 && (c === 0 || c === 2)) ||
|
|
330
|
+
(a === 192 && b === 88 && c === 99) ||
|
|
331
|
+
(a === 192 && b === 168) ||
|
|
332
|
+
(a === 198 && (b === 18 || b === 19)) ||
|
|
333
|
+
(a === 198 && b === 51 && c === 100) ||
|
|
334
|
+
(a === 203 && b === 0 && c === 113) ||
|
|
335
|
+
(a === 168 && b === 63 && c === 129 && d === 16)
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Whether the `n` bytes at `b[at]` are all zero. */
|
|
340
|
+
const outboundZeros = (b: u8[], at: i32, n: i32): boolean => {
|
|
341
|
+
for (let i: i32 = 0; i < n; i += 1) {
|
|
342
|
+
if (b[at + i] !== toU8(0)) {
|
|
343
|
+
return false
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return true
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Whether the first `bits` bits of `p[at]` and `addr[off]` agree. */
|
|
350
|
+
const outboundPrefixMatch = (p: u8[], at: i32, bits: i32, addr: u8[], off: i32): boolean => {
|
|
351
|
+
const whole: i32 = bits >> 3
|
|
352
|
+
for (let i: i32 = 0; i < whole; i += 1) {
|
|
353
|
+
if (p[at + i] !== addr[off + i]) {
|
|
354
|
+
return false
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const rest: i32 = bits & 7
|
|
358
|
+
if (rest === 0) {
|
|
359
|
+
return true
|
|
360
|
+
}
|
|
361
|
+
const mask: i32 = (toI32(0xff) << (8 - rest)) & toI32(0xff)
|
|
362
|
+
return (toI32(p[at + whole]) & mask) === (toI32(addr[off + whole]) & mask)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** `s[from..to)` as a decimal of one to three digits, or -1. */
|
|
366
|
+
const outboundDecimal = (s: string, from: i32, to: i32): i32 => {
|
|
367
|
+
if (to <= from || to - from > 3) {
|
|
368
|
+
return -1
|
|
369
|
+
}
|
|
370
|
+
let v: i32 = 0
|
|
371
|
+
for (let i: i32 = from; i < to; i += 1) {
|
|
372
|
+
const c: i32 = toI32(s.charCodeAt(i))
|
|
373
|
+
if (c < 48 || c > 57) {
|
|
374
|
+
return -1
|
|
375
|
+
}
|
|
376
|
+
v = v * 10 + c - 48
|
|
377
|
+
}
|
|
378
|
+
return v
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Whether `name` is an address literal: it holds a colon, or its last label is all digits. */
|
|
382
|
+
const outboundIsLiteral = (name: string): boolean => {
|
|
383
|
+
if (name.indexOf(":") >= 0) {
|
|
384
|
+
return true
|
|
385
|
+
}
|
|
386
|
+
const n: i32 = toI32(name.length)
|
|
387
|
+
let i: i32 = n - 1
|
|
388
|
+
while (i >= 0 && name.charCodeAt(i) >= 48 && name.charCodeAt(i) <= 57) {
|
|
389
|
+
i -= 1
|
|
390
|
+
}
|
|
391
|
+
return i < n - 1 && (i < 0 || name.charCodeAt(i) === 46)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** `s` with A to Z lower-cased. */
|
|
395
|
+
const outboundLower = (s: string): string => {
|
|
396
|
+
const n: i32 = toI32(s.length)
|
|
397
|
+
const out: u8[] = new Array<u8>(n)
|
|
398
|
+
for (let i: i32 = 0; i < toI32(out.length) && i < n; i += 1) {
|
|
399
|
+
const c: i32 = toI32(s.charCodeAt(i))
|
|
400
|
+
out[i] = c >= 65 && c <= 90 ? toU8(c + 32) : toU8(c)
|
|
401
|
+
}
|
|
402
|
+
return String.fromBytes(out, 0, n)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** `s` without one trailing dot. */
|
|
406
|
+
const outboundTrimDot = (s: string): string => {
|
|
407
|
+
const n: i32 = toI32(s.length)
|
|
408
|
+
return n > 0 && s.charCodeAt(n - 1) === 46 ? s.substring(0, n - 1) : s
|
|
409
|
+
}
|