castrum 0.9.7 → 0.9.9

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 (108) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/castrum.darwin-arm64.node +0 -0
  4. package/castrum.darwin-x64.node +0 -0
  5. package/castrum.linux-arm64-gnu.node +0 -0
  6. package/castrum.linux-arm64-musl.node +0 -0
  7. package/castrum.linux-x64-gnu.node +0 -0
  8. package/castrum.linux-x64-musl.node +0 -0
  9. package/castrum.linux-x64-v3-gnu.node +0 -0
  10. package/castrum.win32-x64-msvc.node +0 -0
  11. package/dist/index.d.ts +664 -8
  12. package/dist/index.js +2014 -1327
  13. package/docs/BENCHMARKS.md +13 -0
  14. package/docs/CASE_STUDY.md +1 -1
  15. package/docs/ENVIRONMENT.md +8 -1
  16. package/docs/FFI_BUN_GUIDE.md +1 -1
  17. package/docs/INGRESS.md +4 -4
  18. package/docs/REPO_MAP.md +7 -5
  19. package/docs/RND-CONCURRENCY.md +20 -4
  20. package/docs/superpowers/plans/2026-09-18-castrum-hardening-cleanup.md +617 -0
  21. package/docs/superpowers/plans/2026-09-18-elysia-learnings-adoption.md +634 -0
  22. package/index.ts +6 -0
  23. package/package.json +4 -2
  24. package/rust/crypto/aead.rs +44 -0
  25. package/rust/crypto/argon2.rs +32 -2
  26. package/rust/crypto/jwt/tests.rs +19 -0
  27. package/rust/crypto/jwt/token.rs +43 -28
  28. package/rust/crypto/session.rs +6 -0
  29. package/rust/ffi/crypto.rs +63 -32
  30. package/rust/ffi/hashing.rs +48 -33
  31. package/rust/ffi/http.rs +62 -36
  32. package/rust/ffi/metrics.rs +6 -2
  33. package/rust/ffi/payload.rs +47 -27
  34. package/rust/ffi/task.rs +55 -35
  35. package/rust/ffi/tests.rs +24 -0
  36. package/rust/ffi/util.rs +40 -0
  37. package/rust/http/headers.rs +13 -0
  38. package/rust/http/multipart.rs +139 -0
  39. package/rust/ingress/api.rs +21 -5
  40. package/rust/ingress/mod.rs +7 -0
  41. package/rust/ingress/native_route.rs +25 -5
  42. package/rust/ingress/schema_cache.rs +266 -0
  43. package/rust/task/completion.rs +7 -0
  44. package/rust/task/mod.rs +2 -1
  45. package/rust/task/runtime.rs +55 -7
  46. package/rust/task/tests.rs +128 -1
  47. package/src/ingress/abort.ts +48 -0
  48. package/src/ingress/body.ts +104 -8
  49. package/src/ingress/decode/baked-result.ts +13 -5
  50. package/src/ingress/decode/fast-result.ts +1 -1
  51. package/src/ingress/decode/result-base.ts +16 -2
  52. package/src/ingress/handlers.ts +104 -31
  53. package/src/ingress/headers/memoized-headers.ts +40 -0
  54. package/src/ingress/health.ts +22 -0
  55. package/src/ingress/index.ts +3 -1
  56. package/src/ingress/native-route.ts +25 -6
  57. package/src/ingress/options.ts +26 -2
  58. package/src/ingress/packing/gather-raw-headers.ts +24 -8
  59. package/src/ingress/response/baked-response.ts +118 -28
  60. package/src/ingress/router.ts +50 -3
  61. package/src/ingress/routes/common.ts +35 -11
  62. package/src/ingress/routes/echo.ts +60 -7
  63. package/src/ingress/routes/json-write.ts +32 -8
  64. package/src/ingress/routes/native.ts +24 -5
  65. package/src/ingress/routes/options.ts +6 -2
  66. package/src/ingress/routes/responder.ts +29 -9
  67. package/src/ingress/routes/safe-record.ts +57 -0
  68. package/src/ingress/server-error.ts +111 -0
  69. package/src/ingress/server-node.ts +135 -37
  70. package/src/ingress/server.ts +143 -36
  71. package/src/ingress/shared.ts +1 -1
  72. package/src/ingress/types.ts +15 -0
  73. package/src/integration/pipeline.ts +6 -0
  74. package/src/loader/ops.ts +1 -1
  75. package/src/native/ffi/build/codecs-selftest.ts +305 -0
  76. package/src/native/ffi/build/codecs.ts +0 -296
  77. package/src/native/ffi/build/instances-selftest.ts +264 -0
  78. package/src/native/ffi/build/instances.ts +10 -259
  79. package/src/native/ffi/build/parse-selftest.ts +238 -0
  80. package/src/native/ffi/build/parse.ts +11 -234
  81. package/src/native/ffi/build/task.ts +2 -1
  82. package/src/native/ffi/build.ts +5 -1
  83. package/src/native/ffi/selftest.ts +3 -3
  84. package/src/native/ffi/symbols.ts +460 -0
  85. package/src/native/ffi/types.ts +6 -1
  86. package/src/native/ffi.ts +2 -434
  87. package/src/native/ingress-binding.ts +39 -0
  88. package/src/native/types.ts +6 -0
  89. package/src/runtime/codec.ts +24 -0
  90. package/src/runtime/detect.ts +14 -0
  91. package/src/runtime/types.ts +11 -0
  92. package/src/rust-ffi/batch/build.ts +18 -8
  93. package/src/rust-ffi/batch/types.ts +15 -0
  94. package/src/rust-ffi/context.ts +46 -3
  95. package/src/rust-ffi/options.ts +19 -0
  96. package/src/rust-ffi/scalar/crypto.ts +8 -0
  97. package/src/rust-ffi/scalar/factories.ts +53 -20
  98. package/src/rust-ffi/scalar/http.ts +60 -10
  99. package/src/rust-ffi/scalar/interface.ts +18 -1
  100. package/src/rust-ffi/scalar/payload.ts +27 -1
  101. package/src/rust-ffi/text.ts +5 -4
  102. package/src/shared/buffer-pool.ts +54 -9
  103. package/src/shared/codec.ts +26 -0
  104. package/src/shared/memory.ts +68 -0
  105. package/src/shared/metrics.ts +86 -3
  106. package/src/shared/packed/parsers.ts +64 -7
  107. package/src/shared/runtime.ts +7 -0
  108. package/src/task/runtime.ts +81 -37
@@ -0,0 +1,305 @@
1
+ // src/native/ffi/build/codecs-selftest.ts — bind-time self-test for the
2
+ // codec/crypto/auth BunFFI surface.
3
+ //
4
+ // Extracted from codecs.ts so the method builders and the known-answer probes
5
+ // stay separate modules; `selftest.ts` ANDs this with the other per-domain
6
+ // probes.
7
+
8
+ import { decodeUtf8, encodeUtf8 } from '../../../shared/codec'
9
+ import { SELFTEST_HEX, SELFTEST_JSON } from '../constants'
10
+ import type { BunFFI } from '../types'
11
+
12
+ /**
13
+ * Bind-time self-test for the codec/crypto/auth surface (the methods built in
14
+ * `buildCodecs`). Exercises every bound function against known-good vectors;
15
+ * `false` disables the ffi layer and forces the napi fallback.
16
+ */
17
+ export function selfTestCodecs(b: BunFFI): boolean {
18
+ const enc = { encode: encodeUtf8 }
19
+ const dec = { decode: decodeUtf8 }
20
+
21
+ // Checksums + validity probes.
22
+ if (b.crc32(enc.encode('123456789')) !== 0xcbf4_3926) return false
23
+ if (b.fnv1a64(enc.encode('foobar')) !== 0x8594_4171_f739_67e8n) return false
24
+ // XXH3-64 of empty input = 0x2d06800538d394c2 (standard reference vector).
25
+ if (b.xxh3(new Uint8Array(0)) !== 0x2d06800538d394c2n) return false
26
+ if (b.jsonValid(SELFTEST_JSON) !== true || b.jsonValid(enc.encode('{not json')) !== false) {
27
+ return false
28
+ }
29
+ // UTF-8 validity probe (native replacement for a fatal TextDecoder on the
30
+ // Bun path — used by the urlDecode wrapper).
31
+ if (!b.utf8Valid(enc.encode('héllo')) || b.utf8Valid(new Uint8Array([0xff, 0xfe]))) {
32
+ return false
33
+ }
34
+ const hexOut = new Uint8Array(SELFTEST_HEX.length * 2)
35
+ if (b.hexEncodeInto(SELFTEST_HEX, hexOut) !== 10 || dec.decode(hexOut) !== '68656c6c6f') {
36
+ return false
37
+ }
38
+ const urlInput = enc.encode('a b/c')
39
+ const urlOut = new Uint8Array(9)
40
+ if (b.urlEncodeInto(urlInput, urlOut) !== 9 || dec.decode(urlOut) !== 'a%20b%2Fc') {
41
+ return false
42
+ }
43
+
44
+ // Validators take `cstring` ARGs (the engine transcodes the JS string).
45
+ if (
46
+ !b.validateEmail('a@b.com') ||
47
+ !b.validateUuid('550e8400-e29b-41d4-a716-446655440000') ||
48
+ !b.validateIpv4('192.168.0.1') ||
49
+ !b.validateIpv6('2001:db8::1') ||
50
+ b.validateEmail('not-an-email') ||
51
+ b.validateUuid('not-a-uuid')
52
+ ) {
53
+ return false
54
+ }
55
+ // Byte-input validator siblings: `(ptr,len)` parity with the string forms.
56
+ if (
57
+ !b.validateEmailBytes(enc.encode('a@b.com')) ||
58
+ !b.validateUuidBytes(enc.encode('550e8400-e29b-41d4-a716-446655440000')) ||
59
+ !b.validateIpv4Bytes(enc.encode('192.168.0.1')) ||
60
+ !b.validateIpv6Bytes(enc.encode('2001:db8::1')) ||
61
+ b.validateEmailBytes(enc.encode('not-an-email')) ||
62
+ b.validateIpv6Bytes(enc.encode('999'))
63
+ ) {
64
+ return false
65
+ }
66
+ // Packed `[u8 ok][i64 sum LE]` ABI: legit zero-sum is ok, invalid input throws.
67
+ if (b.jsonSumIds(enc.encode(`[{"id":1},{"id":2}]`)) !== 3n) return false
68
+ if (b.jsonSumIds(enc.encode(`[{"id":0},{"id":0}]`)) !== 0n) return false
69
+ let sumInvalidThrew = false
70
+ try {
71
+ b.jsonSumIds(enc.encode('nope'))
72
+ } catch {
73
+ sumInvalidThrew = true
74
+ }
75
+ if (!sumInvalidThrew) return false
76
+
77
+ // Batch fixed-width hex validation (needed-size convention + verdicts).
78
+ {
79
+ const ids = enc.encode('507f1f77bcf86cd799439011\nzz\n507F1F77BCF86CD799439012')
80
+ const tiny = new Uint8Array(2)
81
+ const needed = b.hexValidateBatchInto(ids, 24, tiny)
82
+ if (needed !== 3) return false // exact required size on a too-small buffer
83
+ const out = new Uint8Array(needed)
84
+ if (b.hexValidateBatchInto(ids, 24, out) !== 3) return false
85
+ if (out[0] !== 1 || out[1] !== 0 || out[2] !== 1) return false
86
+ let widthThrew = false
87
+ try {
88
+ b.hexValidateBatchInto(ids, 0, new Uint8Array(3))
89
+ } catch {
90
+ widthThrew = true
91
+ }
92
+ if (!widthThrew) return false
93
+ }
94
+
95
+ // RegExp escaping: metachars get backslashes; plain text is untouched.
96
+ {
97
+ const out = new Uint8Array(64)
98
+ const w = b.regexEscapeInto(enc.encode('a.c(x)'), out)
99
+ if (w !== 9 || dec.decode(out.subarray(0, w)) !== 'a\\.c\\(x\\)') return false
100
+ const plain = new Uint8Array(16)
101
+ const wp = b.regexEscapeInto(enc.encode('hello'), plain)
102
+ if (wp !== 5 || dec.decode(plain.subarray(0, wp)) !== 'hello') return false
103
+ // too-small buffer reports the exact required size
104
+ if (b.regexEscapeInto(enc.encode('a.b'), new Uint8Array(2)) !== 4) return false
105
+ // zero-copy str sibling must produce the identical escaped string
106
+ if (b.regexEscapeStr('a.c(x)') !== 'a\\.c\\(x\\)') return false
107
+ if (b.regexEscapeStr('plain text') !== 'plain text') return false
108
+ }
109
+
110
+ // String-input batch hex validation (cstring ARG sibling).
111
+ {
112
+ const out = new Uint8Array(8)
113
+ const w = b.hexValidateBatchStr('507f1f77bcf86cd799439011\nzz', 24, out)
114
+ if (w !== 2 || out[0] !== 1 || out[1] !== 0) return false
115
+ }
116
+
117
+ // HMAC RFC 4231 test case 1 (0x0b × 20 key, "Hi There" data).
118
+ const hmacKey = new Uint8Array(20).fill(0x0b)
119
+ const hmacData = enc.encode('Hi There')
120
+ const hmacSig = enc.encode('b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7')
121
+ if (
122
+ b.hmacSha256(hmacKey, hmacData) !==
123
+ 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'
124
+ ) {
125
+ return false
126
+ }
127
+ if (!b.hmacSha256Verify(hmacKey, hmacData, hmacSig)) {
128
+ return false
129
+ }
130
+
131
+ // Decoders round-trip.
132
+ const decoded = b.hexDecode(enc.encode('68656c6c6f'))
133
+ if (
134
+ dec.decode(decoded) !== 'hello' ||
135
+ dec.decode(b.urlDecode(enc.encode('a%20b%2Fc'))) !== 'a b/c'
136
+ ) {
137
+ return false
138
+ }
139
+
140
+ // WebSocket accept key (RFC 6455 sample) — `key` is a `cstring` ARG.
141
+ if (b.wsAcceptKey('dGhlIHNhbXBsZSBub25jZQ==') !== 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') {
142
+ return false
143
+ }
144
+ // ETag: strong = 10 bytes, weak = 12 bytes.
145
+ if (b.etag(SELFTEST_HEX).length !== 10 || b.etag(SELFTEST_HEX, true).length !== 12) {
146
+ return false
147
+ }
148
+
149
+ // base64.
150
+ if (b.base64Encode(SELFTEST_HEX) !== 'aGVsbG8=') {
151
+ return false
152
+ }
153
+ if (dec.decode(b.base64Decode(enc.encode('aGVsbG8='))) !== 'hello') {
154
+ return false
155
+ }
156
+
157
+ // Signed cookie round-trip.
158
+ const secret = enc.encode('s3cr3t-secret')
159
+ const signed = b.signCookie(SELFTEST_HEX, secret)
160
+ const verified = b.verifyCookie(enc.encode(signed), secret)
161
+ if (verified === null || verified !== 'hello') {
162
+ return false
163
+ }
164
+ if (b.verifyCookie(enc.encode('tampered.0000'), secret) !== null) {
165
+ return false
166
+ }
167
+
168
+ // CSRF token round-trip (issued token verifies against the same secret).
169
+ const csrfTokenStr = b.csrfToken(secret)
170
+ if (csrfTokenStr.length !== 129 || !b.csrfVerify(enc.encode(csrfTokenStr), secret)) {
171
+ return false
172
+ }
173
+
174
+ // Argon2id round-trip at minimum cost (fast) — full defaults would take ~50ms.
175
+ const pw = enc.encode('correct horse battery staple')
176
+ const salt = enc.encode('salty-salt-16b')
177
+ const phc = b.passwordHash(pw, salt, 8, 1, 1, 16)
178
+ if (phc.length === 0 || !b.passwordVerify(pw, enc.encode(phc))) {
179
+ return false
180
+ }
181
+ // bcrypt round-trip at minimum cost (fast) — `phc` is a `cstring` ARG.
182
+ const bcryptPhc = b.passwordHashBcrypt(pw, 4)
183
+ if (bcryptPhc.length === 0 || !b.passwordVerifyBcrypt(pw, bcryptPhc)) {
184
+ return false
185
+ }
186
+ // PBKDF2-HMAC-SHA256: password="password", salt="salt", c=1, dkLen=32.
187
+ const dk = b.pbkdf2Sha256(enc.encode('password'), enc.encode('salt'), 1, 32)
188
+ if (b.hexEncode(dk) !== '120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b') {
189
+ return false
190
+ }
191
+
192
+ // AEAD AES-256-GCM round-trip (key 32B, nonce 12B).
193
+ const aeadKey = new Uint8Array(32).fill(0x42)
194
+ const nonce = new Uint8Array(12).fill(0x07)
195
+ const ct = b.aeadEncrypt(aeadKey, nonce, SELFTEST_HEX, 0)
196
+ const pt = b.aeadDecrypt(aeadKey, nonce, ct, 0)
197
+ if (pt === null || dec.decode(pt) !== 'hello') {
198
+ return false
199
+ }
200
+
201
+ // Random token pooled Into: writes byteLen*2 hex chars into the buffer.
202
+ const rtOut = new Uint8Array(32)
203
+ const rtW = b.randomTokenInto(16, rtOut)
204
+ if (rtW !== 32 || !/^[0-9a-f]{32}$/.test(dec.decode(rtOut))) {
205
+ return false
206
+ }
207
+ // Too-small buffer → throw (needed-size convention).
208
+ try {
209
+ b.randomTokenInto(16, new Uint8Array(8))
210
+ return false
211
+ } catch {
212
+ // expected
213
+ }
214
+
215
+ // Fixed-size writers pooled Into: ws_accept_key + etag + csrf_token match the
216
+ // cstring path byte-for-byte.
217
+ const waOut = new Uint8Array(28)
218
+ const waW = b.wsAcceptKeyInto(enc.encode('dGhlIHNhbXBsZSBub25jZQ=='), waOut)
219
+ const waExpected = b.wsAcceptKey('dGhlIHNhbXBsZSBub25jZQ==')
220
+ if (waW !== 28 || dec.decode(waOut) !== waExpected) {
221
+ return false
222
+ }
223
+ const etagData = enc.encode('hello')
224
+ const etagOut = new Uint8Array(16)
225
+ const etagW = b.etagInto(etagData, etagOut)
226
+ if (etagW !== 10 || dec.decode(etagOut.subarray(0, etagW)) !== b.etag(etagData)) {
227
+ return false
228
+ }
229
+ const csrfOut = new Uint8Array(129)
230
+ const csrfW = b.csrfTokenInto(enc.encode('csrf-secret'), csrfOut)
231
+ if (csrfW !== 129 || csrfOut[64] !== 46 /* '.' */) {
232
+ return false
233
+ }
234
+
235
+ // sign/verify cookie pooled Into: round-trip value bytes.
236
+ const ckVal = enc.encode('session-value')
237
+ const ckSec = enc.encode('s3cr3t-secret')
238
+ const ckOut = new Uint8Array(256)
239
+ const ckW = b.signCookieInto(ckVal, ckSec, ckOut)
240
+ if (ckW !== ckVal.length + 65) {
241
+ return false
242
+ }
243
+ const ckVerifyOut = new Uint8Array(256)
244
+ const ckV = b.verifyCookieInto(ckOut.subarray(0, ckW), ckSec, ckVerifyOut)
245
+ if (ckV !== ckVal.length || dec.decode(ckVerifyOut.subarray(0, ckV)) !== 'session-value') {
246
+ return false
247
+ }
248
+
249
+ // urlEncode string form.
250
+ if (b.urlEncode(enc.encode('a b/c')) !== 'a%20b%2Fc') {
251
+ return false
252
+ }
253
+ // hexDecodeInto.
254
+ const hdInto = new Uint8Array(8)
255
+ if (
256
+ b.hexDecodeInto(enc.encode('68656c6c6f'), hdInto) !== 5 ||
257
+ dec.decode(hdInto.subarray(0, 5)) !== 'hello'
258
+ ) {
259
+ return false
260
+ }
261
+ // urlDecodeInto.
262
+ const udInto = new Uint8Array(16)
263
+ if (
264
+ b.urlDecodeInto(enc.encode('a%20b%2Fc'), udInto) !== 5 ||
265
+ dec.decode(udInto.subarray(0, 5)) !== 'a b/c'
266
+ ) {
267
+ return false
268
+ }
269
+ // base64EncodeInto.
270
+ const b64Into = new Uint8Array(16)
271
+ if (
272
+ b.base64EncodeInto(SELFTEST_HEX, b64Into) !== 8 ||
273
+ dec.decode(b64Into.subarray(0, 8)) !== 'aGVsbG8='
274
+ ) {
275
+ return false
276
+ }
277
+ // base64DecodeInto.
278
+ const bdInto = new Uint8Array(8)
279
+ if (
280
+ b.base64DecodeInto(enc.encode('aGVsbG8='), bdInto) !== 5 ||
281
+ dec.decode(bdInto.subarray(0, 5)) !== 'hello'
282
+ ) {
283
+ return false
284
+ }
285
+ // randomToken string form.
286
+ if (!/^[0-9a-f]{32}$/.test(b.randomToken(16))) {
287
+ return false
288
+ }
289
+ // hmacSha256Into (RFC 4231 test case 1 hex vector).
290
+ const hmacInto = new Uint8Array(64)
291
+ if (
292
+ b.hmacSha256Into(hmacKey, hmacData, hmacInto) !== 64 ||
293
+ dec.decode(hmacInto) !== 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'
294
+ ) {
295
+ return false
296
+ }
297
+ // aeadEncryptInto → decrypt round-trip.
298
+ const ctInto = new Uint8Array(64)
299
+ const ctW = b.aeadEncryptInto(aeadKey, nonce, SELFTEST_HEX, ctInto, 0)
300
+ if (ctW === 0 || b.aeadDecrypt(aeadKey, nonce, ctInto.subarray(0, ctW), 0) === null) {
301
+ return false
302
+ }
303
+
304
+ return true
305
+ }
@@ -6,7 +6,6 @@
6
6
  // the per-bind context (argument adapters + pooled scratch) from `build()`.
7
7
 
8
8
  import { decodeUtf8, encodeUtf8 } from '../../../shared/codec'
9
- import { SELFTEST_HEX, SELFTEST_JSON } from '../constants'
10
9
  import type { BunFFI, Raw3, Raw4, Raw5, Raw6, Raw8, Raw9, Raw10, RawCStr } from '../types'
11
10
  import type { BuildCtx } from './util'
12
11
  import { allocOut, argon2PhcLength, cstr, flag, growExact, hasNul, writeOrThrow } from './util'
@@ -557,298 +556,3 @@ export function buildCodecs(
557
556
  },
558
557
  }
559
558
  }
560
-
561
- /**
562
- * Bind-time self-test for the codec/crypto/auth surface (the methods built in
563
- * `buildCodecs`). Exercises every bound function against known-good vectors;
564
- * `false` disables the ffi layer and forces the napi fallback.
565
- */
566
- export function selfTestCodecs(b: BunFFI): boolean {
567
- const enc = { encode: encodeUtf8 }
568
- const dec = { decode: decodeUtf8 }
569
-
570
- // Checksums + validity probes.
571
- if (b.crc32(enc.encode('123456789')) !== 0xcbf4_3926) return false
572
- if (b.fnv1a64(enc.encode('foobar')) !== 0x8594_4171_f739_67e8n) return false
573
- // XXH3-64 of empty input = 0x2d06800538d394c2 (standard reference vector).
574
- if (b.xxh3(new Uint8Array(0)) !== 0x2d06800538d394c2n) return false
575
- if (b.jsonValid(SELFTEST_JSON) !== true || b.jsonValid(enc.encode('{not json')) !== false) {
576
- return false
577
- }
578
- // UTF-8 validity probe (native replacement for a fatal TextDecoder on the
579
- // Bun path — used by the urlDecode wrapper).
580
- if (!b.utf8Valid(enc.encode('héllo')) || b.utf8Valid(new Uint8Array([0xff, 0xfe]))) {
581
- return false
582
- }
583
- const hexOut = new Uint8Array(SELFTEST_HEX.length * 2)
584
- if (b.hexEncodeInto(SELFTEST_HEX, hexOut) !== 10 || dec.decode(hexOut) !== '68656c6c6f') {
585
- return false
586
- }
587
- const urlInput = enc.encode('a b/c')
588
- const urlOut = new Uint8Array(9)
589
- if (b.urlEncodeInto(urlInput, urlOut) !== 9 || dec.decode(urlOut) !== 'a%20b%2Fc') {
590
- return false
591
- }
592
-
593
- // Validators take `cstring` ARGs (the engine transcodes the JS string).
594
- if (
595
- !b.validateEmail('a@b.com') ||
596
- !b.validateUuid('550e8400-e29b-41d4-a716-446655440000') ||
597
- !b.validateIpv4('192.168.0.1') ||
598
- !b.validateIpv6('2001:db8::1') ||
599
- b.validateEmail('not-an-email') ||
600
- b.validateUuid('not-a-uuid')
601
- ) {
602
- return false
603
- }
604
- // Byte-input validator siblings: `(ptr,len)` parity with the string forms.
605
- if (
606
- !b.validateEmailBytes(enc.encode('a@b.com')) ||
607
- !b.validateUuidBytes(enc.encode('550e8400-e29b-41d4-a716-446655440000')) ||
608
- !b.validateIpv4Bytes(enc.encode('192.168.0.1')) ||
609
- !b.validateIpv6Bytes(enc.encode('2001:db8::1')) ||
610
- b.validateEmailBytes(enc.encode('not-an-email')) ||
611
- b.validateIpv6Bytes(enc.encode('999'))
612
- ) {
613
- return false
614
- }
615
- // Packed `[u8 ok][i64 sum LE]` ABI: legit zero-sum is ok, invalid input throws.
616
- if (b.jsonSumIds(enc.encode(`[{"id":1},{"id":2}]`)) !== 3n) return false
617
- if (b.jsonSumIds(enc.encode(`[{"id":0},{"id":0}]`)) !== 0n) return false
618
- let sumInvalidThrew = false
619
- try {
620
- b.jsonSumIds(enc.encode('nope'))
621
- } catch {
622
- sumInvalidThrew = true
623
- }
624
- if (!sumInvalidThrew) return false
625
-
626
- // Batch fixed-width hex validation (needed-size convention + verdicts).
627
- {
628
- const ids = enc.encode('507f1f77bcf86cd799439011\nzz\n507F1F77BCF86CD799439012')
629
- const tiny = new Uint8Array(2)
630
- const needed = b.hexValidateBatchInto(ids, 24, tiny)
631
- if (needed !== 3) return false // exact required size on a too-small buffer
632
- const out = new Uint8Array(needed)
633
- if (b.hexValidateBatchInto(ids, 24, out) !== 3) return false
634
- if (out[0] !== 1 || out[1] !== 0 || out[2] !== 1) return false
635
- let widthThrew = false
636
- try {
637
- b.hexValidateBatchInto(ids, 0, new Uint8Array(3))
638
- } catch {
639
- widthThrew = true
640
- }
641
- if (!widthThrew) return false
642
- }
643
-
644
- // RegExp escaping: metachars get backslashes; plain text is untouched.
645
- {
646
- const out = new Uint8Array(64)
647
- const w = b.regexEscapeInto(enc.encode('a.c(x)'), out)
648
- if (w !== 9 || dec.decode(out.subarray(0, w)) !== 'a\\.c\\(x\\)') return false
649
- const plain = new Uint8Array(16)
650
- const wp = b.regexEscapeInto(enc.encode('hello'), plain)
651
- if (wp !== 5 || dec.decode(plain.subarray(0, wp)) !== 'hello') return false
652
- // too-small buffer reports the exact required size
653
- if (b.regexEscapeInto(enc.encode('a.b'), new Uint8Array(2)) !== 4) return false
654
- // zero-copy str sibling must produce the identical escaped string
655
- if (b.regexEscapeStr('a.c(x)') !== 'a\\.c\\(x\\)') return false
656
- if (b.regexEscapeStr('plain text') !== 'plain text') return false
657
- }
658
-
659
- // String-input batch hex validation (cstring ARG sibling).
660
- {
661
- const out = new Uint8Array(8)
662
- const w = b.hexValidateBatchStr('507f1f77bcf86cd799439011\nzz', 24, out)
663
- if (w !== 2 || out[0] !== 1 || out[1] !== 0) return false
664
- }
665
-
666
- // HMAC RFC 4231 test case 1 (0x0b × 20 key, "Hi There" data).
667
- const hmacKey = new Uint8Array(20).fill(0x0b)
668
- const hmacData = enc.encode('Hi There')
669
- const hmacSig = enc.encode('b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7')
670
- if (
671
- b.hmacSha256(hmacKey, hmacData) !==
672
- 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'
673
- ) {
674
- return false
675
- }
676
- if (!b.hmacSha256Verify(hmacKey, hmacData, hmacSig)) {
677
- return false
678
- }
679
-
680
- // Decoders round-trip.
681
- const decoded = b.hexDecode(enc.encode('68656c6c6f'))
682
- if (
683
- dec.decode(decoded) !== 'hello' ||
684
- dec.decode(b.urlDecode(enc.encode('a%20b%2Fc'))) !== 'a b/c'
685
- ) {
686
- return false
687
- }
688
-
689
- // WebSocket accept key (RFC 6455 sample) — `key` is a `cstring` ARG.
690
- if (b.wsAcceptKey('dGhlIHNhbXBsZSBub25jZQ==') !== 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') {
691
- return false
692
- }
693
- // ETag: strong = 10 bytes, weak = 12 bytes.
694
- if (b.etag(SELFTEST_HEX).length !== 10 || b.etag(SELFTEST_HEX, true).length !== 12) {
695
- return false
696
- }
697
-
698
- // base64.
699
- if (b.base64Encode(SELFTEST_HEX) !== 'aGVsbG8=') {
700
- return false
701
- }
702
- if (dec.decode(b.base64Decode(enc.encode('aGVsbG8='))) !== 'hello') {
703
- return false
704
- }
705
-
706
- // Signed cookie round-trip.
707
- const secret = enc.encode('s3cr3t-secret')
708
- const signed = b.signCookie(SELFTEST_HEX, secret)
709
- const verified = b.verifyCookie(enc.encode(signed), secret)
710
- if (verified === null || verified !== 'hello') {
711
- return false
712
- }
713
- if (b.verifyCookie(enc.encode('tampered.0000'), secret) !== null) {
714
- return false
715
- }
716
-
717
- // CSRF token round-trip (issued token verifies against the same secret).
718
- const csrfTokenStr = b.csrfToken(secret)
719
- if (csrfTokenStr.length !== 129 || !b.csrfVerify(enc.encode(csrfTokenStr), secret)) {
720
- return false
721
- }
722
-
723
- // Argon2id round-trip at minimum cost (fast) — full defaults would take ~50ms.
724
- const pw = enc.encode('correct horse battery staple')
725
- const salt = enc.encode('salty-salt-16b')
726
- const phc = b.passwordHash(pw, salt, 8, 1, 1, 16)
727
- if (phc.length === 0 || !b.passwordVerify(pw, enc.encode(phc))) {
728
- return false
729
- }
730
- // bcrypt round-trip at minimum cost (fast) — `phc` is a `cstring` ARG.
731
- const bcryptPhc = b.passwordHashBcrypt(pw, 4)
732
- if (bcryptPhc.length === 0 || !b.passwordVerifyBcrypt(pw, bcryptPhc)) {
733
- return false
734
- }
735
- // PBKDF2-HMAC-SHA256: password="password", salt="salt", c=1, dkLen=32.
736
- const dk = b.pbkdf2Sha256(enc.encode('password'), enc.encode('salt'), 1, 32)
737
- if (b.hexEncode(dk) !== '120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b') {
738
- return false
739
- }
740
-
741
- // AEAD AES-256-GCM round-trip (key 32B, nonce 12B).
742
- const aeadKey = new Uint8Array(32).fill(0x42)
743
- const nonce = new Uint8Array(12).fill(0x07)
744
- const ct = b.aeadEncrypt(aeadKey, nonce, SELFTEST_HEX, 0)
745
- const pt = b.aeadDecrypt(aeadKey, nonce, ct, 0)
746
- if (pt === null || dec.decode(pt) !== 'hello') {
747
- return false
748
- }
749
-
750
- // Random token pooled Into: writes byteLen*2 hex chars into the buffer.
751
- const rtOut = new Uint8Array(32)
752
- const rtW = b.randomTokenInto(16, rtOut)
753
- if (rtW !== 32 || !/^[0-9a-f]{32}$/.test(dec.decode(rtOut))) {
754
- return false
755
- }
756
- // Too-small buffer → throw (needed-size convention).
757
- try {
758
- b.randomTokenInto(16, new Uint8Array(8))
759
- return false
760
- } catch {
761
- // expected
762
- }
763
-
764
- // Fixed-size writers pooled Into: ws_accept_key + etag + csrf_token match the
765
- // cstring path byte-for-byte.
766
- const waOut = new Uint8Array(28)
767
- const waW = b.wsAcceptKeyInto(enc.encode('dGhlIHNhbXBsZSBub25jZQ=='), waOut)
768
- const waExpected = b.wsAcceptKey('dGhlIHNhbXBsZSBub25jZQ==')
769
- if (waW !== 28 || dec.decode(waOut) !== waExpected) {
770
- return false
771
- }
772
- const etagData = enc.encode('hello')
773
- const etagOut = new Uint8Array(16)
774
- const etagW = b.etagInto(etagData, etagOut)
775
- if (etagW !== 10 || dec.decode(etagOut.subarray(0, etagW)) !== b.etag(etagData)) {
776
- return false
777
- }
778
- const csrfOut = new Uint8Array(129)
779
- const csrfW = b.csrfTokenInto(enc.encode('csrf-secret'), csrfOut)
780
- if (csrfW !== 129 || csrfOut[64] !== 46 /* '.' */) {
781
- return false
782
- }
783
-
784
- // sign/verify cookie pooled Into: round-trip value bytes.
785
- const ckVal = enc.encode('session-value')
786
- const ckSec = enc.encode('s3cr3t-secret')
787
- const ckOut = new Uint8Array(256)
788
- const ckW = b.signCookieInto(ckVal, ckSec, ckOut)
789
- if (ckW !== ckVal.length + 65) {
790
- return false
791
- }
792
- const ckVerifyOut = new Uint8Array(256)
793
- const ckV = b.verifyCookieInto(ckOut.subarray(0, ckW), ckSec, ckVerifyOut)
794
- if (ckV !== ckVal.length || dec.decode(ckVerifyOut.subarray(0, ckV)) !== 'session-value') {
795
- return false
796
- }
797
-
798
- // urlEncode string form.
799
- if (b.urlEncode(enc.encode('a b/c')) !== 'a%20b%2Fc') {
800
- return false
801
- }
802
- // hexDecodeInto.
803
- const hdInto = new Uint8Array(8)
804
- if (
805
- b.hexDecodeInto(enc.encode('68656c6c6f'), hdInto) !== 5 ||
806
- dec.decode(hdInto.subarray(0, 5)) !== 'hello'
807
- ) {
808
- return false
809
- }
810
- // urlDecodeInto.
811
- const udInto = new Uint8Array(16)
812
- if (
813
- b.urlDecodeInto(enc.encode('a%20b%2Fc'), udInto) !== 5 ||
814
- dec.decode(udInto.subarray(0, 5)) !== 'a b/c'
815
- ) {
816
- return false
817
- }
818
- // base64EncodeInto.
819
- const b64Into = new Uint8Array(16)
820
- if (
821
- b.base64EncodeInto(SELFTEST_HEX, b64Into) !== 8 ||
822
- dec.decode(b64Into.subarray(0, 8)) !== 'aGVsbG8='
823
- ) {
824
- return false
825
- }
826
- // base64DecodeInto.
827
- const bdInto = new Uint8Array(8)
828
- if (
829
- b.base64DecodeInto(enc.encode('aGVsbG8='), bdInto) !== 5 ||
830
- dec.decode(bdInto.subarray(0, 5)) !== 'hello'
831
- ) {
832
- return false
833
- }
834
- // randomToken string form.
835
- if (!/^[0-9a-f]{32}$/.test(b.randomToken(16))) {
836
- return false
837
- }
838
- // hmacSha256Into (RFC 4231 test case 1 hex vector).
839
- const hmacInto = new Uint8Array(64)
840
- if (
841
- b.hmacSha256Into(hmacKey, hmacData, hmacInto) !== 64 ||
842
- dec.decode(hmacInto) !== 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'
843
- ) {
844
- return false
845
- }
846
- // aeadEncryptInto → decrypt round-trip.
847
- const ctInto = new Uint8Array(64)
848
- const ctW = b.aeadEncryptInto(aeadKey, nonce, SELFTEST_HEX, ctInto, 0)
849
- if (ctW === 0 || b.aeadDecrypt(aeadKey, nonce, ctInto.subarray(0, ctW), 0) === null) {
850
- return false
851
- }
852
-
853
- return true
854
- }