@amritk/nish-x86_64-linux 0.17.0 → 0.20.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 (61) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +231 -20
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +1376 -42
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1412 -70
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
@@ -0,0 +1,676 @@
1
+ /**
2
+ * `nish/net/router` — a method and path router for HTTP servers, independent
3
+ * of the carrier: the same table answers an HTTP/1 request line and an
4
+ * HTTP/2 or HTTP/3 `:path`.
5
+ *
6
+ * const HOME: i32 = 0 // module constants, in the order the routes are added
7
+ * const USER: i32 = 1
8
+ * const router = new Router(new RouterLimits())
9
+ * if (router.add("GET", "/") !== HOME || router.add("GET", "/users/:id") !== USER) panic(...)
10
+ * // per request, with `params` made once beside the connection:
11
+ * switch (router.match(p.head, p.methodStart, p.methodEnd, p.head, p.targetStart, p.targetEnd, params)) {
12
+ * case HOME: ... case USER: ... case ROUTER_NOT_FOUND: ...
13
+ * }
14
+ *
15
+ * Nish has no function values, so a route is the index `add` answered, 0 for
16
+ * the first and one more for each after, and the program dispatches on it
17
+ * with `switch`, whose labels are module constants. A parameter is answered as a
18
+ * `[start, end)` pair of offsets into the target, never as a copy, and
19
+ * `routerDecode` percent-decodes one of them into a buffer the caller owns.
20
+ *
21
+ * **The table is built once; a match allocates nothing.** `add` refuses a
22
+ * duplicate, an ambiguous or an over-long pattern with a negative code and
23
+ * adds nothing. `match` walks flat `i32` arrays the table grew and writes into
24
+ * scratch the constructor sized, so a warmed server moves the arena by
25
+ * nothing per request. A `Router` is therefore not reentrant: one match at a
26
+ * time, which is what a connection loop does.
27
+ *
28
+ * **The rules** (`docs/wp43-http-app-layer.md` §2):
29
+ *
30
+ * - The path is the target up to its first `?` (the query is left alone) and
31
+ * must start with `/`: the `*` of `OPTIONS *` and the authority form of
32
+ * `CONNECT` match nothing, so the program answers them before it routes.
33
+ * - A path is split on `/` into segments; `/a/` has two, `a` and an empty one.
34
+ * A trailing slash is significant, and matching is on raw bytes, so `%2F`
35
+ * never splits a segment.
36
+ * - A pattern segment is a literal, a parameter `:name` (one non-empty
37
+ * segment), or `*` as the last segment (the rest of the path, possibly
38
+ * empty, as one more pair). Precedence is literal, then parameter, then
39
+ * wildcard, segment by segment from the left; a branch that fails further
40
+ * down falls back to the next one. Every edge consumes one segment, so a
41
+ * lookup visits each node of the table at most once.
42
+ * - A path that matches only routes of other methods answers
43
+ * `ROUTER_METHOD_NOT_ALLOWED`, so a server can tell 405 from 404.
44
+ *
45
+ * Private helpers share the importing program's flat symbol namespace
46
+ * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
47
+ */
48
+
49
+ /** `add`: the pattern is empty, does not start with `/`, or misuses `:`, `*`, `%`, `?` or a byte outside `!` to `~`. */
50
+ export const ROUTER_BAD_PATTERN: i32 = -1
51
+ /** `add`: the method is not a token of 1 to 32 bytes. */
52
+ export const ROUTER_BAD_METHOD: i32 = -2
53
+ /** `add`: the pattern is over `RouterLimits`' bytes, segments or parameters. */
54
+ export const ROUTER_TOO_LONG: i32 = -3
55
+ /** `add`: a `*` that is not the last segment, where how much it takes would be a guess. */
56
+ export const ROUTER_AMBIGUOUS: i32 = -4
57
+ /** `add`: the method already has a route of this shape, whatever its parameters are called. */
58
+ export const ROUTER_DUPLICATE: i32 = -5
59
+ /** `add`: the table holds `RouterLimits.maxRoutes` routes or `maxMethods` methods already. */
60
+ export const ROUTER_FULL: i32 = -6
61
+ /** `match`: no route's pattern matches the path, or the target is not `/`-rooted. */
62
+ export const ROUTER_NOT_FOUND: i32 = -7
63
+ /** `match`: the path matches only routes of other methods. */
64
+ export const ROUTER_METHOD_NOT_ALLOWED: i32 = -8
65
+ /** `match`: the route matched, but its parameters need more than `params` holds; nothing was written. */
66
+ export const ROUTER_TOO_MANY_PARAMS: i32 = -9
67
+ /** `routerDecode`: a `%` not followed by two hex digits. */
68
+ export const ROUTER_BAD_ESCAPE: i32 = -10
69
+ /** `routerDecode`: the decoded bytes do not fit `out`; nothing was written. */
70
+ export const ROUTER_NO_ROOM: i32 = -11
71
+
72
+ /** The longest method `add` takes, as `nish/net/http1`'s `HTTP1_MAX_METHOD`. */
73
+ const ROUTER_MAX_METHOD: i32 = 32
74
+ /** No node or route: an `i32` spelled once, so that `--number-mode f64` does not read a bare `-1` as a double. */
75
+ const ROUTER_NONE: i32 = -1
76
+ /** A pattern segment's kind, in `Router.add`'s scratch. */
77
+ const ROUTER_LITERAL: i32 = 0
78
+ const ROUTER_PARAM: i32 = 1
79
+ const ROUTER_WILD: i32 = 2
80
+
81
+ /**
82
+ * What a `Router` holds and refuses past. Each field has a default a server
83
+ * can use as it is; the constructor panics on a value no table could work with.
84
+ */
85
+ export class RouterLimits {
86
+ /** The longest pattern, in bytes (1 to 65536). */
87
+ maxPatternBytes: i32 = 1024
88
+ /** The most segments in one pattern (1 to 256): the deepest a route can be. */
89
+ maxSegments: i32 = 32
90
+ /** The most parameters in one pattern, a final `*` counted (0 to 256). */
91
+ maxParams: i32 = 16
92
+ /** The most routes in the table (1 to 1048576). */
93
+ maxRoutes: i32 = 4096
94
+ /** The most distinct methods (1 to 64). */
95
+ maxMethods: i32 = 16
96
+ }
97
+
98
+ /**
99
+ * A route table: a tree whose edges are path segments, kept in flat arrays.
100
+ * Node 0 is the root, the point before the first segment; a node at depth `d`
101
+ * has consumed `d` segments.
102
+ */
103
+ export class Router {
104
+ // References, then 32-bit fields: the order the layout packs without padding.
105
+ /** Per node: its first literal child, or -1; siblings chain through `nextSibling`. */
106
+ firstChild: i32[]
107
+ /** Per node: the next literal child of its parent, or -1. */
108
+ nextSibling: i32[]
109
+ /** Per node: its literal label, `labels[labelStart .. labelEnd)`; empty for a parameter or `*` node. */
110
+ labelStart: i32[]
111
+ labelEnd: i32[]
112
+ /** Per node: its parameter child, or -1; one per node, since names take no part in matching. */
113
+ paramChild: i32[]
114
+ /** Per node: its `*` child, or -1. */
115
+ wildChild: i32[]
116
+ /** Per node: the first route ending there, or -1; routes chain through `endpointNext`. */
117
+ firstEndpoint: i32[]
118
+ /** Every literal label, back to back. */
119
+ labels: u8[]
120
+ /** Per route: its method's index into `methodStart`, the next route ending at its node, and its parameter pairs. */
121
+ routeMethod: i32[]
122
+ endpointNext: i32[]
123
+ routeParams: i32[]
124
+ /** The methods the table knows, `methodBytes[methodStart[m] .. methodEnd[m])`, compared exactly (RFC 9110 §9.1). */
125
+ methodBytes: u8[]
126
+ methodStart: i32[]
127
+ methodEnd: i32[]
128
+ /** `match`'s scratch, sized by the constructor: each segment's span, then the pairs found so far. */
129
+ segStart: i32[]
130
+ segEnd: i32[]
131
+ found: i32[]
132
+ /** `add`'s scratch: each pattern segment's kind and span in the pattern. */
133
+ patKind: i32[]
134
+ patStart: i32[]
135
+ patEnd: i32[]
136
+ /** The limits, copied out of the `RouterLimits` the table was made with. */
137
+ maxPatternBytes: i32 = 0
138
+ maxSegments: i32 = 0
139
+ maxParams: i32 = 0
140
+ maxRoutes: i32 = 0
141
+ maxMethods: i32 = 0
142
+ /** The most segments any route has: a path is split no further than one past it. */
143
+ deepest: i32 = 0
144
+ /** During a match: how many segments the path has, where it ends, the method's index, and the pairs in `found`. */
145
+ segments: i32 = 0
146
+ pathEnd: i32 = 0
147
+ wanted: i32 = 0
148
+ foundCount: i32 = 0
149
+ /** During a match: whether a route of another method ended at the path's end. */
150
+ otherMethod: boolean = false
151
+ /** The nodes the last `match` visited: never more than `nodeCount()`, which the tests pin. */
152
+ visited: i32 = 0
153
+
154
+ /** An empty table. Panics, naming the field, on a limit outside the range `RouterLimits` gives it. */
155
+ constructor(limits: RouterLimits) {
156
+ routerLimit("maxPatternBytes", limits.maxPatternBytes, 1, 65536)
157
+ routerLimit("maxSegments", limits.maxSegments, 1, 256)
158
+ routerLimit("maxParams", limits.maxParams, 0, 256)
159
+ routerLimit("maxRoutes", limits.maxRoutes, 1, 1048576)
160
+ routerLimit("maxMethods", limits.maxMethods, 1, 64)
161
+ this.maxPatternBytes = limits.maxPatternBytes
162
+ this.maxSegments = limits.maxSegments
163
+ this.maxParams = limits.maxParams
164
+ this.maxRoutes = limits.maxRoutes
165
+ this.maxMethods = limits.maxMethods
166
+ this.firstChild = [-1]
167
+ this.nextSibling = [-1]
168
+ this.labelStart = [0]
169
+ this.labelEnd = [0]
170
+ this.paramChild = [-1]
171
+ this.wildChild = [-1]
172
+ this.firstEndpoint = [-1]
173
+ this.labels = []
174
+ this.routeMethod = []
175
+ this.endpointNext = []
176
+ this.routeParams = []
177
+ this.methodBytes = []
178
+ this.methodStart = []
179
+ this.methodEnd = []
180
+ this.segStart = new Array<i32>(limits.maxSegments + 1)
181
+ this.segEnd = new Array<i32>(limits.maxSegments + 1)
182
+ this.found = new Array<i32>(2 * limits.maxParams)
183
+ this.patKind = new Array<i32>(limits.maxSegments)
184
+ this.patStart = new Array<i32>(limits.maxSegments)
185
+ this.patEnd = new Array<i32>(limits.maxSegments)
186
+ }
187
+
188
+ /** How many routes the table holds; the next `add` answers this index. */
189
+ routes(): i32 {
190
+ return toI32(this.routeMethod.length)
191
+ }
192
+
193
+ /** How many nodes the tree has, the root included: the most one `match` visits. */
194
+ nodeCount(): i32 {
195
+ return toI32(this.firstChild.length)
196
+ }
197
+
198
+ /** How many `[start, end)` pairs `route` answers: its parameters, and one more for a final `*`. */
199
+ paramCount(route: i32): i32 {
200
+ if (route < 0 || route >= this.routes()) {
201
+ panic(`Router.paramCount: route ${route} is not in a table of ${this.routes()}`)
202
+ }
203
+ return this.routeParams[route]
204
+ }
205
+
206
+ /** How many nodes the last `match` visited. */
207
+ lastVisited(): i32 {
208
+ return this.visited
209
+ }
210
+
211
+ /**
212
+ * Adds the route `method pattern` and answers its index, counting from 0, or
213
+ * a negative `ROUTER_*` code; a refused pattern adds nothing. Whether a
214
+ * pattern is refused never depends on the order routes were added in.
215
+ */
216
+ add(method: string, pattern: string): i32 {
217
+ const methodLength: i32 = toI32(method.length)
218
+ if (methodLength < 1 || methodLength > ROUTER_MAX_METHOD) {
219
+ return ROUTER_BAD_METHOD
220
+ }
221
+ for (let k: i32 = 0; k < methodLength; k++) {
222
+ if (!routerTchar(toI32(method.charCodeAt(k)))) {
223
+ return ROUTER_BAD_METHOD
224
+ }
225
+ }
226
+ const length: i32 = toI32(pattern.length)
227
+ if (length > this.maxPatternBytes) {
228
+ return ROUTER_TOO_LONG
229
+ }
230
+ if (length === 0 || toI32(pattern.charCodeAt(0)) !== 47) {
231
+ return ROUTER_BAD_PATTERN
232
+ }
233
+ const count: i32 = this.splitPattern(pattern)
234
+ if (count < 0) {
235
+ return count
236
+ }
237
+ let params: i32 = 0
238
+ for (let k: i32 = 0; k < count; k++) {
239
+ if (this.patKind[k] !== ROUTER_LITERAL) {
240
+ params += 1
241
+ }
242
+ }
243
+ if (params > this.maxParams) {
244
+ return ROUTER_TOO_LONG
245
+ }
246
+ // The walk that finds where the pattern ends, adding nothing yet: a
247
+ // duplicate is a pattern whose every node already exists.
248
+ let node: i32 = 0
249
+ let depth: i32 = 0
250
+ while (depth < count && node >= 0) {
251
+ node = this.childFor(node, pattern, depth)
252
+ depth += 1
253
+ }
254
+ const known: i32 = this.methodIndex(method)
255
+ if (node >= 0 && known >= 0) {
256
+ for (let e: i32 = this.firstEndpoint[node]; e >= 0; e = this.endpointNext[e]) {
257
+ if (this.routeMethod[e] === known) {
258
+ return ROUTER_DUPLICATE
259
+ }
260
+ }
261
+ }
262
+ if (this.routes() >= this.maxRoutes || (known < 0 && toI32(this.methodStart.length) >= this.maxMethods)) {
263
+ return ROUTER_FULL
264
+ }
265
+ let methodId: i32 = known
266
+ if (methodId < 0) {
267
+ methodId = toI32(this.methodStart.length)
268
+ this.methodStart.push(toI32(this.methodBytes.length))
269
+ for (let k: i32 = 0; k < methodLength; k++) {
270
+ this.methodBytes.push(toU8(toI32(method.charCodeAt(k))))
271
+ }
272
+ this.methodEnd.push(toI32(this.methodBytes.length))
273
+ }
274
+ node = 0
275
+ for (let k: i32 = 0; k < count; k++) {
276
+ const next: i32 = this.childFor(node, pattern, k)
277
+ node = next >= 0 ? next : this.addChild(node, pattern, k)
278
+ }
279
+ const route: i32 = this.routes()
280
+ this.routeMethod.push(methodId)
281
+ this.routeParams.push(params)
282
+ this.endpointNext.push(this.firstEndpoint[node])
283
+ this.firstEndpoint[node] = route
284
+ if (count > this.deepest) {
285
+ this.deepest = count
286
+ }
287
+ return route
288
+ }
289
+
290
+ /**
291
+ * Splits `pattern`, which starts with `/`, into `patKind`/`patStart`/`patEnd`
292
+ * and answers how many segments it has, or the code that refuses it.
293
+ */
294
+ splitPattern(pattern: string): i32 {
295
+ const length: i32 = toI32(pattern.length)
296
+ let count: i32 = 0
297
+ let start: i32 = 1
298
+ for (let i: i32 = 1; i <= length; i++) {
299
+ const c: i32 = i < length ? toI32(pattern.charCodeAt(i)) : 47
300
+ if (c < 33 || c > 126 || c === 37 || c === 63) {
301
+ return ROUTER_BAD_PATTERN
302
+ }
303
+ if (c !== 47) {
304
+ continue
305
+ }
306
+ if (count === this.maxSegments) {
307
+ return ROUTER_TOO_LONG
308
+ }
309
+ if (count > 0 && this.patKind[count - 1] === ROUTER_WILD) {
310
+ return ROUTER_AMBIGUOUS
311
+ }
312
+ const kind: i32 = routerSegmentKind(pattern, start, i)
313
+ if (kind < 0) {
314
+ return kind
315
+ }
316
+ this.patKind[count] = kind
317
+ this.patStart[count] = kind === ROUTER_PARAM ? start + 1 : start
318
+ this.patEnd[count] = i
319
+ if (kind === ROUTER_PARAM) {
320
+ for (let k: i32 = 0; k < count; k++) {
321
+ if (
322
+ this.patKind[k] === ROUTER_PARAM &&
323
+ routerSameName(pattern, this.patStart[k], this.patEnd[k], start + 1, i)
324
+ ) {
325
+ return ROUTER_BAD_PATTERN
326
+ }
327
+ }
328
+ }
329
+ count += 1
330
+ start = i + 1
331
+ }
332
+ return count
333
+ }
334
+
335
+ /** The child of `node` that pattern segment `k` leads to, or -1. */
336
+ childFor(node: i32, pattern: string, k: i32): i32 {
337
+ const kind: i32 = this.patKind[k]
338
+ if (kind === ROUTER_PARAM) {
339
+ return this.paramChild[node]
340
+ }
341
+ if (kind === ROUTER_WILD) {
342
+ return this.wildChild[node]
343
+ }
344
+ const start: i32 = this.patStart[k]
345
+ const length: i32 = this.patEnd[k] - start
346
+ for (let c: i32 = this.firstChild[node]; c >= 0; c = this.nextSibling[c]) {
347
+ const at: i32 = this.labelStart[c]
348
+ if (this.labelEnd[c] - at === length && routerLabelIsText(this.labels, at, pattern, start, length)) {
349
+ return c
350
+ }
351
+ }
352
+ return -1
353
+ }
354
+
355
+ /** Adds the child of `node` that pattern segment `k` leads to, and answers it. */
356
+ addChild(node: i32, pattern: string, k: i32): i32 {
357
+ const child: i32 = this.nodeCount()
358
+ const kind: i32 = this.patKind[k]
359
+ const at: i32 = toI32(this.labels.length)
360
+ this.firstChild.push(ROUTER_NONE)
361
+ this.nextSibling.push(ROUTER_NONE)
362
+ this.paramChild.push(ROUTER_NONE)
363
+ this.wildChild.push(ROUTER_NONE)
364
+ this.firstEndpoint.push(ROUTER_NONE)
365
+ this.labelStart.push(at)
366
+ if (kind === ROUTER_LITERAL) {
367
+ const n: i32 = toI32(pattern.length)
368
+ for (let i: i32 = this.patStart[k]; i >= 0 && i < this.patEnd[k] && i < n; i++) {
369
+ this.labels.push(toU8(toI32(pattern.charCodeAt(i))))
370
+ }
371
+ this.nextSibling[child] = this.firstChild[node]
372
+ this.firstChild[node] = child
373
+ } else if (kind === ROUTER_PARAM) {
374
+ this.paramChild[node] = child
375
+ } else {
376
+ this.wildChild[node] = child
377
+ }
378
+ this.labelEnd.push(toI32(this.labels.length))
379
+ return child
380
+ }
381
+
382
+ /** The index of `method` among the methods the table knows, or -1. */
383
+ methodIndex(method: string): i32 {
384
+ const length: i32 = toI32(method.length)
385
+ for (let m: i32 = 0; m < toI32(this.methodStart.length); m++) {
386
+ const at: i32 = this.methodStart[m]
387
+ if (this.methodEnd[m] - at === length && routerLabelIsText(this.methodBytes, at, method, 0, length)) {
388
+ return m
389
+ }
390
+ }
391
+ return -1
392
+ }
393
+
394
+ /**
395
+ * The route `method[mStart .. mEnd)` and `target[tStart .. tEnd)` reach, or
396
+ * a negative `ROUTER_*` code. On a route, its parameters are written to
397
+ * `params` as `[start, end)` offsets into `target`, two slots each, in the
398
+ * pattern's order, and a final `*` as one more pair; on a refusal `params`
399
+ * is untouched. Panics on a range outside its array.
400
+ */
401
+ match(method: u8[], mStart: i32, mEnd: i32, target: u8[], tStart: i32, tEnd: i32, params: i32[]): i32 {
402
+ if (mStart < 0 || mEnd < mStart || mEnd > toI32(method.length)) {
403
+ panic(`Router.match: the method range ${mStart} to ${mEnd} is outside its ${method.length} bytes`)
404
+ }
405
+ if (tStart < 0 || tEnd < tStart || tEnd > toI32(target.length)) {
406
+ panic(`Router.match: the target range ${tStart} to ${tEnd} is outside its ${target.length} bytes`)
407
+ }
408
+ this.visited = 0
409
+ if (tStart === tEnd || toI32(target[tStart]) !== 47) {
410
+ return ROUTER_NOT_FOUND
411
+ }
412
+ // One pass over the path: where it ends, and each segment's span up to one
413
+ // past the deepest route, which is as far as any edge can reach.
414
+ let end: i32 = tStart
415
+ while (end < tEnd && toI32(target[end]) !== 63) {
416
+ end += 1
417
+ }
418
+ const keep: i32 = this.deepest + 1
419
+ let segments: i32 = 0
420
+ let start: i32 = tStart + 1
421
+ for (let i: i32 = tStart + 1; i <= end; i++) {
422
+ if (i >= 0 && i < end && i < toI32(target.length) && toI32(target[i]) !== 47) {
423
+ continue
424
+ }
425
+ if (segments < keep) {
426
+ this.segStart[segments] = start
427
+ this.segEnd[segments] = i
428
+ }
429
+ segments += 1
430
+ start = i + 1
431
+ }
432
+ this.segments = segments
433
+ this.pathEnd = end
434
+ this.wanted = -1
435
+ const methodLength: i32 = mEnd - mStart
436
+ for (let m: i32 = 0; m < toI32(this.methodStart.length) && this.wanted < 0; m++) {
437
+ const at: i32 = this.methodStart[m]
438
+ if (
439
+ this.methodEnd[m] - at === methodLength &&
440
+ routerSameBytes(this.methodBytes, at, method, mStart, methodLength)
441
+ ) {
442
+ this.wanted = m
443
+ }
444
+ }
445
+ this.foundCount = 0
446
+ this.otherMethod = false
447
+ const route: i32 = this.walk(target, toI32(0), toI32(0))
448
+ if (route < 0) {
449
+ return this.otherMethod ? ROUTER_METHOD_NOT_ALLOWED : ROUTER_NOT_FOUND
450
+ }
451
+ const pairs: i32 = this.foundCount
452
+ if (2 * pairs > toI32(params.length)) {
453
+ return ROUTER_TOO_MANY_PARAMS
454
+ }
455
+ for (let k: i32 = 0; k < 2 * pairs && k < toI32(params.length); k++) {
456
+ params[k] = this.found[k]
457
+ }
458
+ return route
459
+ }
460
+
461
+ /**
462
+ * The route below `node`, which has consumed `depth` segments, for the
463
+ * wanted method, or -1. Literal, then parameter, then `*`: the first branch
464
+ * that reaches a route wins, and a branch that does not is left with the
465
+ * pairs it pushed popped.
466
+ */
467
+ walk(target: u8[], node: i32, depth: i32): i32 {
468
+ this.visited += 1
469
+ if (depth === this.segments) {
470
+ return this.endpointFor(node)
471
+ }
472
+ const start: i32 = this.segStart[depth]
473
+ const end: i32 = this.segEnd[depth]
474
+ const length: i32 = end - start
475
+ for (let c: i32 = this.firstChild[node]; c >= 0; c = this.nextSibling[c]) {
476
+ const at: i32 = this.labelStart[c]
477
+ if (this.labelEnd[c] - at === length && routerSameBytes(this.labels, at, target, start, length)) {
478
+ const route: i32 = this.walk(target, c, depth + 1)
479
+ if (route >= 0) {
480
+ return route
481
+ }
482
+ // Labels under one node are distinct, so no other literal child can match.
483
+ break
484
+ }
485
+ }
486
+ const param: i32 = this.paramChild[node]
487
+ if (param >= 0 && length > 0) {
488
+ const top: i32 = this.foundCount
489
+ this.found[2 * top] = start
490
+ this.found[2 * top + 1] = end
491
+ this.foundCount = top + 1
492
+ const route: i32 = this.walk(target, param, depth + 1)
493
+ if (route >= 0) {
494
+ return route
495
+ }
496
+ this.foundCount = top
497
+ }
498
+ const wild: i32 = this.wildChild[node]
499
+ if (wild >= 0) {
500
+ this.visited += 1
501
+ const route: i32 = this.endpointFor(wild)
502
+ if (route >= 0) {
503
+ const top: i32 = this.foundCount
504
+ this.found[2 * top] = start
505
+ this.found[2 * top + 1] = this.pathEnd
506
+ this.foundCount = top + 1
507
+ return route
508
+ }
509
+ }
510
+ return -1
511
+ }
512
+
513
+ /** The route of the wanted method ending at `node`, or -1, noting a route of another method there. */
514
+ endpointFor(node: i32): i32 {
515
+ for (let e: i32 = this.firstEndpoint[node]; e >= 0; e = this.endpointNext[e]) {
516
+ if (this.routeMethod[e] === this.wanted) {
517
+ return e
518
+ }
519
+ this.otherMethod = true
520
+ }
521
+ return -1
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Percent-decodes `target[start .. end)`, one range `match` answered, into
527
+ * `out` from 0, and answers the decoded length or a negative `ROUTER_*` code;
528
+ * on a code nothing is written. `%2F` becomes `/` here, inside the one
529
+ * parameter, because `match` split the path before anything was decoded. `+`
530
+ * stays `+`: a space spelled `+` is the query's form encoding, not the path's.
531
+ * The output may hold any byte, NUL and `/` included. Panics on a range
532
+ * outside `target`.
533
+ */
534
+ export const routerDecode = (target: u8[], start: i32, end: i32, out: u8[]): i32 => {
535
+ if (start < 0 || end < start || end > toI32(target.length)) {
536
+ panic(`routerDecode: the range ${start} to ${end} is outside its ${target.length} bytes`)
537
+ }
538
+ let length: i32 = 0
539
+ let i: i32 = start
540
+ while (i < end) {
541
+ if (toI32(target[i]) === 37) {
542
+ if (i + 2 >= end || routerHex(toI32(target[i + 1])) < 0 || routerHex(toI32(target[i + 2])) < 0) {
543
+ return ROUTER_BAD_ESCAPE
544
+ }
545
+ i += 3
546
+ } else {
547
+ i += 1
548
+ }
549
+ length += 1
550
+ }
551
+ if (length > toI32(out.length)) {
552
+ return ROUTER_NO_ROOM
553
+ }
554
+ let at: i32 = 0
555
+ i = start
556
+ const room: i32 = toI32(out.length)
557
+ const n: i32 = toI32(target.length)
558
+ while (i >= 0 && i < end && i < n && at >= 0 && at < room) {
559
+ const c: i32 = toI32(target[i])
560
+ if (c === 37 && i + 2 < n) {
561
+ out[at] = toU8(routerHex(toI32(target[i + 1])) * 16 + routerHex(toI32(target[i + 2])))
562
+ i += 3
563
+ } else {
564
+ out[at] = target[i]
565
+ i += 1
566
+ }
567
+ at += 1
568
+ }
569
+ return length
570
+ }
571
+
572
+ /** Panics, naming `RouterLimits.<field>`, unless `value` is in `low .. high`. */
573
+ const routerLimit = (field: string, value: i32, low: i32, high: i32): void => {
574
+ if (value < low || value > high) {
575
+ panic(`RouterLimits.${field}: ${value} is outside ${low} to ${high}`)
576
+ }
577
+ }
578
+
579
+ /** Whether `c` is a token character (RFC 9110 §5.6.2), what a method is made of. */
580
+ const routerTchar = (c: i32): boolean => {
581
+ if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122)) {
582
+ return true
583
+ }
584
+ return (
585
+ c === 33 ||
586
+ c === 35 ||
587
+ c === 36 ||
588
+ c === 37 ||
589
+ c === 38 ||
590
+ c === 39 ||
591
+ c === 42 ||
592
+ c === 43 ||
593
+ c === 45 ||
594
+ c === 46 ||
595
+ c === 94 ||
596
+ c === 95 ||
597
+ c === 96 ||
598
+ c === 124 ||
599
+ c === 126
600
+ )
601
+ }
602
+
603
+ /**
604
+ * The kind of the pattern segment `pattern[start .. end)`, or
605
+ * `ROUTER_BAD_PATTERN`: a parameter is `:` and a name of letters, digits and
606
+ * `_`; `*` is the whole segment or nowhere in it; anything else is a literal.
607
+ */
608
+ const routerSegmentKind = (pattern: string, start: i32, end: i32): i32 => {
609
+ if (end - start === 1 && toI32(pattern.charCodeAt(start)) === 42) {
610
+ return ROUTER_WILD
611
+ }
612
+ const param: boolean = end > start && toI32(pattern.charCodeAt(start)) === 58
613
+ if (param && end - start === 1) {
614
+ return ROUTER_BAD_PATTERN
615
+ }
616
+ const n: i32 = toI32(pattern.length)
617
+ for (let i: i32 = start; i >= 0 && i < end && i < n; i++) {
618
+ const c: i32 = toI32(pattern.charCodeAt(i))
619
+ if (c === 42) {
620
+ return ROUTER_BAD_PATTERN
621
+ }
622
+ const nameByte: boolean =
623
+ (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95
624
+ if (param && i > start && !nameByte) {
625
+ return ROUTER_BAD_PATTERN
626
+ }
627
+ }
628
+ return param ? ROUTER_PARAM : ROUTER_LITERAL
629
+ }
630
+
631
+ /** Whether the names `pattern[a .. aEnd)` and `pattern[b .. bEnd)` are the same. */
632
+ const routerSameName = (pattern: string, a: i32, aEnd: i32, b: i32, bEnd: i32): boolean => {
633
+ if (aEnd - a !== bEnd - b) {
634
+ return false
635
+ }
636
+ for (let k: i32 = 0; k < aEnd - a; k++) {
637
+ if (pattern.charCodeAt(a + k) !== pattern.charCodeAt(b + k)) {
638
+ return false
639
+ }
640
+ }
641
+ return true
642
+ }
643
+
644
+ /** Whether `bytes[at .. at + length)` spells `text[from .. from + length)`. */
645
+ const routerLabelIsText = (bytes: u8[], at: i32, text: string, from: i32, length: i32): boolean => {
646
+ for (let k: i32 = 0; k < length; k++) {
647
+ if (toI32(bytes[at + k]) !== toI32(text.charCodeAt(from + k))) {
648
+ return false
649
+ }
650
+ }
651
+ return true
652
+ }
653
+
654
+ /** Whether `a[at .. at + length)` and `b[from .. from + length)` hold the same bytes. */
655
+ const routerSameBytes = (a: u8[], at: i32, b: u8[], from: i32, length: i32): boolean => {
656
+ for (let k: i32 = 0; k < length; k++) {
657
+ if (a[at + k] !== b[from + k]) {
658
+ return false
659
+ }
660
+ }
661
+ return true
662
+ }
663
+
664
+ /** The value of the hex digit `c`, in either case, or -1. */
665
+ const routerHex = (c: i32): i32 => {
666
+ if (c >= 48 && c <= 57) {
667
+ return c - 48
668
+ }
669
+ if (c >= 65 && c <= 70) {
670
+ return c - 55
671
+ }
672
+ if (c >= 97 && c <= 102) {
673
+ return c - 87
674
+ }
675
+ return -1
676
+ }