castrum 0.9.7 → 0.9.8
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/castrum.darwin-arm64.node +0 -0
- package/castrum.darwin-x64.node +0 -0
- package/castrum.linux-arm64-gnu.node +0 -0
- package/castrum.linux-arm64-musl.node +0 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/castrum.linux-x64-musl.node +0 -0
- package/castrum.linux-x64-v3-gnu.node +0 -0
- package/castrum.win32-x64-msvc.node +0 -0
- package/dist/index.d.ts +501 -5
- package/dist/index.js +1648 -1325
- package/docs/BENCHMARKS.md +13 -0
- package/docs/CASE_STUDY.md +1 -1
- package/docs/ENVIRONMENT.md +1 -1
- package/docs/FFI_BUN_GUIDE.md +1 -1
- package/docs/INGRESS.md +4 -4
- package/docs/REPO_MAP.md +7 -5
- package/docs/superpowers/plans/2026-09-18-castrum-hardening-cleanup.md +617 -0
- package/index.ts +1 -0
- package/package.json +3 -2
- package/rust/crypto/aead.rs +44 -0
- package/rust/crypto/argon2.rs +32 -2
- package/rust/crypto/jwt/tests.rs +19 -0
- package/rust/crypto/jwt/token.rs +43 -28
- package/rust/crypto/session.rs +6 -0
- package/rust/ffi/crypto.rs +63 -32
- package/rust/ffi/hashing.rs +48 -33
- package/rust/ffi/http.rs +62 -36
- package/rust/ffi/metrics.rs +6 -2
- package/rust/ffi/payload.rs +47 -27
- package/rust/ffi/task.rs +51 -32
- package/rust/ffi/tests.rs +24 -0
- package/rust/ffi/util.rs +40 -0
- package/rust/http/headers.rs +13 -0
- package/rust/http/multipart.rs +139 -0
- package/rust/ingress/native_route.rs +21 -3
- package/rust/task/completion.rs +7 -0
- package/rust/task/mod.rs +1 -1
- package/rust/task/tests.rs +30 -0
- package/src/ingress/decode/baked-result.ts +13 -5
- package/src/ingress/decode/fast-result.ts +1 -1
- package/src/ingress/decode/result-base.ts +16 -2
- package/src/ingress/handlers.ts +103 -31
- package/src/ingress/headers/memoized-headers.ts +40 -0
- package/src/ingress/native-route.ts +25 -6
- package/src/ingress/options.ts +26 -2
- package/src/ingress/packing/gather-raw-headers.ts +24 -8
- package/src/ingress/response/baked-response.ts +118 -28
- package/src/ingress/router.ts +14 -2
- package/src/ingress/routes/common.ts +26 -10
- package/src/ingress/routes/echo.ts +50 -7
- package/src/ingress/routes/json-write.ts +21 -8
- package/src/ingress/routes/responder.ts +4 -4
- package/src/ingress/server-node.ts +33 -14
- package/src/ingress/server.ts +3 -4
- package/src/ingress/shared.ts +1 -1
- package/src/ingress/types.ts +15 -0
- package/src/loader/ops.ts +1 -1
- package/src/native/ffi/build/codecs-selftest.ts +305 -0
- package/src/native/ffi/build/codecs.ts +0 -296
- package/src/native/ffi/build/instances-selftest.ts +264 -0
- package/src/native/ffi/build/instances.ts +10 -259
- package/src/native/ffi/build/parse-selftest.ts +238 -0
- package/src/native/ffi/build/parse.ts +11 -234
- package/src/native/ffi/build.ts +5 -1
- package/src/native/ffi/selftest.ts +3 -3
- package/src/native/ffi/symbols.ts +460 -0
- package/src/native/ffi.ts +2 -434
- package/src/native/ingress-binding.ts +39 -0
- package/src/runtime/codec.ts +24 -0
- package/src/runtime/types.ts +11 -0
- package/src/rust-ffi/batch/build.ts +18 -8
- package/src/rust-ffi/batch/types.ts +15 -0
- package/src/rust-ffi/options.ts +19 -0
- package/src/rust-ffi/scalar/crypto.ts +8 -0
- package/src/rust-ffi/scalar/factories.ts +47 -20
- package/src/rust-ffi/scalar/http.ts +60 -10
- package/src/rust-ffi/scalar/interface.ts +4 -1
- package/src/rust-ffi/scalar/payload.ts +27 -1
- package/src/shared/buffer-pool.ts +44 -9
- package/src/shared/codec.ts +26 -0
- package/src/shared/packed/parsers.ts +64 -7
- package/src/task/runtime.ts +29 -28
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// src/native/ffi/symbols.ts — the bun:ffi dlopen symbol map for the castrum C ABI.
|
|
2
|
+
//
|
|
3
|
+
// Every `#[no_mangle] extern "C"` export in rust/ffi/ is declared here with its
|
|
4
|
+
// exact ABI. Extracted from ffi.ts so the (large, declarative) ABI contract
|
|
5
|
+
// lives apart from the bind-time orchestration: `abi()` rewrites each `ptr`
|
|
6
|
+
// (plus its paired `usize`) to the atomic `buffer`/`buffer_length` pair when the
|
|
7
|
+
// engine supports it, and `u64_fast` avoids BigInt boxing on byte counts.
|
|
8
|
+
|
|
9
|
+
import type { FFITypeOrString } from 'bun:ffi'
|
|
10
|
+
|
|
11
|
+
/** One dlopen symbol entry: its argument ABI and return type. */
|
|
12
|
+
export type FFISymbolSpec = {
|
|
13
|
+
args: readonly FFITypeOrString[]
|
|
14
|
+
returns: FFITypeOrString
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build the dlopen symbol map for the castrum C ABI.
|
|
19
|
+
*
|
|
20
|
+
* @param useBufferLength - When true, `(ptr,len)` pairs become the atomic
|
|
21
|
+
* `buffer`/`buffer_length` ABI pair; otherwise they stay `(ptr,usize)`.
|
|
22
|
+
* @returns The symbol-name to ABI-spec map passed to `dlopen`.
|
|
23
|
+
*/
|
|
24
|
+
export function buildFFISymbolMap(useBufferLength: boolean): Record<string, FFISymbolSpec> {
|
|
25
|
+
// String ABI specs ("ptr"/"usize"/"u32"...): accepted by bun:ffi and typed
|
|
26
|
+
// via `FFITypeOrString` in bun-types. usize == uint64 on the host ABI.
|
|
27
|
+
// Symbol keys must be the exact `#[no_mangle] extern "C"` names in
|
|
28
|
+
// rust/ffi/. Every `(ptr,len)` pair is passed as `(view, view.length)`.
|
|
29
|
+
// The 8 input-only single-buffer fns use `inputAbi`; everything else keeps
|
|
30
|
+
// `(ptr,len)` because the output length is the caller's write cap.
|
|
31
|
+
const inputAbi: readonly FFITypeOrString[] = useBufferLength
|
|
32
|
+
? (['buffer', 'buffer_length'] as unknown as readonly FFITypeOrString[])
|
|
33
|
+
: ['ptr', 'usize']
|
|
34
|
+
// Phase 1.1: extend `buffer`/`buffer_length` to EVERY `(ptr,len)` pair, not
|
|
35
|
+
// just the input-only fns. The engine reads ptr + byteLength off the SAME
|
|
36
|
+
// view at call time (atomic snapshot), so call sites pass the view twice
|
|
37
|
+
// (see `lenOrView` below). Conversion is POSITIONAL and pair-aware: each
|
|
38
|
+
// `ptr` consumes its following `usize` as a length. Scalar args that happen
|
|
39
|
+
// to be `usize` (the opaque `inner` handle of `castrum_ingress_handle_packed`
|
|
40
|
+
// and the `max` param of gzip/brotli decompress) are passed through
|
|
41
|
+
// unchanged — a blind `usize`→`buffer_length` map would corrupt them.
|
|
42
|
+
const abi = (shape: readonly string[]): readonly FFITypeOrString[] => {
|
|
43
|
+
if (!useBufferLength) return shape as readonly FFITypeOrString[]
|
|
44
|
+
const out: FFITypeOrString[] = []
|
|
45
|
+
for (let i = 0; i < shape.length; i++) {
|
|
46
|
+
const t = shape[i]
|
|
47
|
+
if (t === 'ptr') {
|
|
48
|
+
out.push('buffer' as unknown as FFITypeOrString)
|
|
49
|
+
out.push('buffer_length' as unknown as FFITypeOrString)
|
|
50
|
+
i++ // consume the paired `usize`
|
|
51
|
+
} else {
|
|
52
|
+
out.push(t as FFITypeOrString)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out
|
|
56
|
+
}
|
|
57
|
+
// `u64_fast` returns a plain `number` when the value fits (< 2^53) instead
|
|
58
|
+
// of a BigInt — bun-types lacks the literal, so bind it via a typed const
|
|
59
|
+
// (same cast pattern as the `buffer_length` probe above). Every bytes-written
|
|
60
|
+
// return fits in a double, so this removes the per-call BigInt boxing on the
|
|
61
|
+
// hot scalar / `*_into` / packed-parser / ingress calls.
|
|
62
|
+
const U64_FAST = 'u64_fast' as unknown as FFITypeOrString
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
castrum_crc32: { args: inputAbi, returns: 'u32' },
|
|
66
|
+
castrum_fnv1a64: { args: inputAbi, returns: 'u64' },
|
|
67
|
+
castrum_xxh3: { args: inputAbi, returns: 'u64' },
|
|
68
|
+
castrum_json_valid: { args: inputAbi, returns: 'u8' },
|
|
69
|
+
castrum_utf8_valid: { args: inputAbi, returns: 'u8' },
|
|
70
|
+
castrum_hex_encode: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
71
|
+
castrum_hex_decode: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
72
|
+
castrum_url_encode: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
73
|
+
castrum_url_decode: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
74
|
+
// String-input validators: `cstring` ARG (the engine transcodes the JS
|
|
75
|
+
// string in-engine — no JS-side encode; Rust borrows via CStr::from_ptr).
|
|
76
|
+
// The `_bytes` siblings take a `(ptr,len)` pair — zero transcode when
|
|
77
|
+
// the caller already holds bytes (a cstring arg would force a decode +
|
|
78
|
+
// engine re-encode; docs/FFI_BUN_GUIDE.md §3 shape 4).
|
|
79
|
+
//
|
|
80
|
+
// SAFETY RULE for every `cstring` ARG added here: the transcode is
|
|
81
|
+
// NUL-TERMINATED, so an embedded U+0000 truncates the value in-engine. A
|
|
82
|
+
// verdict op bound this way can be fooled (`validateEmail` on
|
|
83
|
+
// 'a@b.com\0<script>' returned TRUE before the byte re-route). Either
|
|
84
|
+
// give the op a `_bytes` sibling and route byte callers to it, or guard
|
|
85
|
+
// the string form with `hasNul` — see docs/FFI_BUN_GUIDE.md §6.1,
|
|
86
|
+
// pinned by test/unit/native/cstring-nul.test.ts.
|
|
87
|
+
castrum_validate_email: { args: ['cstring'], returns: 'u8' },
|
|
88
|
+
castrum_validate_uuid: { args: ['cstring'], returns: 'u8' },
|
|
89
|
+
castrum_validate_ipv4: { args: ['cstring'], returns: 'u8' },
|
|
90
|
+
castrum_validate_ipv6: { args: ['cstring'], returns: 'u8' },
|
|
91
|
+
castrum_validate_email_bytes: { args: inputAbi, returns: 'u8' },
|
|
92
|
+
castrum_validate_uuid_bytes: { args: inputAbi, returns: 'u8' },
|
|
93
|
+
castrum_validate_ipv4_bytes: { args: inputAbi, returns: 'u8' },
|
|
94
|
+
castrum_validate_ipv6_bytes: { args: inputAbi, returns: 'u8' },
|
|
95
|
+
castrum_json_sum_ids: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
96
|
+
castrum_hmac_sha256_verify: {
|
|
97
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
98
|
+
returns: 'u8',
|
|
99
|
+
},
|
|
100
|
+
castrum_csrf_verify: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: 'u8' },
|
|
101
|
+
castrum_password_verify: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: 'u8' },
|
|
102
|
+
castrum_password_verify_bcrypt: {
|
|
103
|
+
// `phc` crosses as a `cstring` ARG; `password` stays a `(ptr,len)` pair.
|
|
104
|
+
args: abi(['ptr', 'usize', 'cstring']),
|
|
105
|
+
returns: 'u8',
|
|
106
|
+
},
|
|
107
|
+
castrum_ws_accept_key: { args: ['cstring'], returns: 'cstring' },
|
|
108
|
+
castrum_ws_accept_key_into: {
|
|
109
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
110
|
+
returns: U64_FAST,
|
|
111
|
+
},
|
|
112
|
+
castrum_etag: { args: abi(['ptr', 'usize', 'u8']), returns: 'cstring' },
|
|
113
|
+
castrum_etag_into: { args: abi(['ptr', 'usize', 'u8', 'ptr', 'usize']), returns: U64_FAST },
|
|
114
|
+
castrum_conditional_is_not_modified: {
|
|
115
|
+
args: abi(['usize', 'ptr', 'usize', 'ptr', 'usize', 'u8']),
|
|
116
|
+
returns: 'u8',
|
|
117
|
+
},
|
|
118
|
+
castrum_media_type_matcher_matches: {
|
|
119
|
+
args: abi(['usize', 'ptr', 'usize']),
|
|
120
|
+
returns: 'u8',
|
|
121
|
+
},
|
|
122
|
+
castrum_accept_negotiator_negotiate: {
|
|
123
|
+
args: abi(['usize', 'ptr', 'usize']),
|
|
124
|
+
returns: 'cstring',
|
|
125
|
+
},
|
|
126
|
+
// Server-preference sibling (RFC 7231 server semantics): `header` is a
|
|
127
|
+
// `cstring` ARG — the engine transcodes the JS header string in-engine.
|
|
128
|
+
castrum_accept_negotiator_negotiate_server: {
|
|
129
|
+
args: ['usize', 'cstring'],
|
|
130
|
+
returns: 'cstring',
|
|
131
|
+
},
|
|
132
|
+
castrum_jwt_signer_sign: {
|
|
133
|
+
args: abi(['usize', 'ptr', 'usize', 'i64', 'ptr', 'usize']),
|
|
134
|
+
returns: U64_FAST,
|
|
135
|
+
},
|
|
136
|
+
castrum_jwt_signer_verify: {
|
|
137
|
+
args: abi(['usize', 'ptr', 'usize', 'i64', 'ptr', 'usize']),
|
|
138
|
+
returns: U64_FAST,
|
|
139
|
+
},
|
|
140
|
+
castrum_template_render: {
|
|
141
|
+
args: abi(['usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
142
|
+
returns: U64_FAST,
|
|
143
|
+
},
|
|
144
|
+
castrum_schema_validator_validate: {
|
|
145
|
+
args: abi(['usize', 'ptr', 'usize']),
|
|
146
|
+
returns: 'u8',
|
|
147
|
+
},
|
|
148
|
+
castrum_rate_limiter_check: {
|
|
149
|
+
// `key` crosses as a `cstring` ARG (text, no NUL in the intended use).
|
|
150
|
+
args: abi(['usize', 'cstring', 'i64', 'ptr', 'usize']),
|
|
151
|
+
returns: U64_FAST,
|
|
152
|
+
},
|
|
153
|
+
castrum_rate_limiter_check_key: {
|
|
154
|
+
args: abi(['usize', 'i64', 'i64', 'ptr', 'usize']),
|
|
155
|
+
returns: U64_FAST,
|
|
156
|
+
},
|
|
157
|
+
castrum_random_token: { args: ['u32'], returns: 'cstring' },
|
|
158
|
+
castrum_random_token_into: { args: ['u32', ...abi(['ptr', 'usize'])], returns: U64_FAST },
|
|
159
|
+
castrum_base64_encode: {
|
|
160
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'u8', 'u8']),
|
|
161
|
+
returns: U64_FAST,
|
|
162
|
+
},
|
|
163
|
+
castrum_base64_decode: {
|
|
164
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'u8', 'u8']),
|
|
165
|
+
returns: U64_FAST,
|
|
166
|
+
},
|
|
167
|
+
castrum_hmac_sha256: {
|
|
168
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
169
|
+
returns: U64_FAST,
|
|
170
|
+
},
|
|
171
|
+
castrum_sign_cookie: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: 'cstring' },
|
|
172
|
+
castrum_sign_cookie_into: {
|
|
173
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
174
|
+
returns: U64_FAST,
|
|
175
|
+
},
|
|
176
|
+
castrum_verify_cookie: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: 'cstring' },
|
|
177
|
+
castrum_verify_cookie_into: {
|
|
178
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
179
|
+
returns: U64_FAST,
|
|
180
|
+
},
|
|
181
|
+
castrum_csrf_token: { args: abi(['ptr', 'usize']), returns: 'cstring' },
|
|
182
|
+
castrum_csrf_token_into: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
183
|
+
castrum_password_hash: {
|
|
184
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'u32', 'u32', 'u32', 'u32', 'ptr', 'usize']),
|
|
185
|
+
returns: U64_FAST,
|
|
186
|
+
},
|
|
187
|
+
castrum_password_hash_bcrypt: {
|
|
188
|
+
args: abi(['ptr', 'usize', 'u32', 'ptr', 'usize']),
|
|
189
|
+
returns: U64_FAST,
|
|
190
|
+
},
|
|
191
|
+
castrum_pbkdf2_sha256: {
|
|
192
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'u32', 'u32', 'ptr', 'usize']),
|
|
193
|
+
returns: U64_FAST,
|
|
194
|
+
},
|
|
195
|
+
castrum_aead_encrypt: {
|
|
196
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize', 'u8', 'ptr', 'usize']),
|
|
197
|
+
returns: U64_FAST,
|
|
198
|
+
},
|
|
199
|
+
castrum_aead_decrypt: {
|
|
200
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize', 'u8', 'ptr', 'usize']),
|
|
201
|
+
returns: U64_FAST,
|
|
202
|
+
},
|
|
203
|
+
castrum_ws_frame_encode: {
|
|
204
|
+
args: abi(['u8', 'ptr', 'usize', 'u8', 'u8', 'ptr', 'usize']),
|
|
205
|
+
returns: U64_FAST,
|
|
206
|
+
},
|
|
207
|
+
castrum_json_patch: {
|
|
208
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
209
|
+
returns: U64_FAST,
|
|
210
|
+
},
|
|
211
|
+
castrum_gzip_compress: {
|
|
212
|
+
args: abi(['ptr', 'usize', 'u32', 'ptr', 'usize']),
|
|
213
|
+
returns: U64_FAST,
|
|
214
|
+
},
|
|
215
|
+
castrum_gzip_decompress: {
|
|
216
|
+
args: abi(['ptr', 'usize', 'usize', 'ptr', 'usize']),
|
|
217
|
+
returns: U64_FAST,
|
|
218
|
+
},
|
|
219
|
+
castrum_brotli_compress: {
|
|
220
|
+
args: abi(['ptr', 'usize', 'u32', 'ptr', 'usize']),
|
|
221
|
+
returns: U64_FAST,
|
|
222
|
+
},
|
|
223
|
+
castrum_brotli_decompress: {
|
|
224
|
+
args: abi(['ptr', 'usize', 'usize', 'ptr', 'usize']),
|
|
225
|
+
returns: U64_FAST,
|
|
226
|
+
},
|
|
227
|
+
castrum_gzip_isize: { args: abi(['ptr', 'usize']), returns: 'u32' },
|
|
228
|
+
castrum_http_parse_request_packed: {
|
|
229
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
230
|
+
returns: U64_FAST,
|
|
231
|
+
},
|
|
232
|
+
castrum_query_parse_packed: {
|
|
233
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
234
|
+
returns: U64_FAST,
|
|
235
|
+
},
|
|
236
|
+
castrum_cookie_parse_packed: {
|
|
237
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
238
|
+
returns: U64_FAST,
|
|
239
|
+
},
|
|
240
|
+
castrum_http_date_into: { args: ['f64', ...abi(['ptr', 'usize'])], returns: U64_FAST },
|
|
241
|
+
castrum_sse_encode_into: {
|
|
242
|
+
// event/id stay `(ptr,len)` byte args. A cstring-ARG conversion was
|
|
243
|
+
// tried (2026-08-23) and reverted: the win was unproven once the
|
|
244
|
+
// codec encode path got faster, and ABI-desync segfaults during the
|
|
245
|
+
// experiment were misread as an engine bug — see docs/FFI_BUN_GUIDE.md
|
|
246
|
+
// §14 before touching this signature.
|
|
247
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize', 'u8', 'u32', 'ptr', 'usize']),
|
|
248
|
+
returns: U64_FAST,
|
|
249
|
+
},
|
|
250
|
+
// ── Excluded-surface additions ──
|
|
251
|
+
castrum_jwt_sign_bytes: {
|
|
252
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'i64', 'i64']),
|
|
253
|
+
returns: 'cstring',
|
|
254
|
+
},
|
|
255
|
+
castrum_jwt_sign_bytes_into: {
|
|
256
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'i64', 'i64', 'ptr', 'usize']),
|
|
257
|
+
returns: U64_FAST,
|
|
258
|
+
},
|
|
259
|
+
// ── Ed25519 / EdDSA JWT (RBAC auth) — bound + self-tested; the public
|
|
260
|
+
// surface is deferred to the auth-module consumer ────────────────
|
|
261
|
+
castrum_ed25519_generate_keypair: {
|
|
262
|
+
args: abi(['ptr', 'usize']),
|
|
263
|
+
returns: U64_FAST,
|
|
264
|
+
},
|
|
265
|
+
castrum_ed25519_sign: {
|
|
266
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
267
|
+
returns: U64_FAST,
|
|
268
|
+
},
|
|
269
|
+
castrum_ed25519_verify: {
|
|
270
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
271
|
+
returns: 'u32',
|
|
272
|
+
},
|
|
273
|
+
castrum_jwt_eddsa_sign: {
|
|
274
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'i64', 'i64']),
|
|
275
|
+
returns: 'cstring',
|
|
276
|
+
},
|
|
277
|
+
castrum_jwt_eddsa_verify: {
|
|
278
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'i64']),
|
|
279
|
+
returns: 'cstring',
|
|
280
|
+
},
|
|
281
|
+
castrum_ws_frame_decode_packed: {
|
|
282
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
283
|
+
returns: U64_FAST,
|
|
284
|
+
},
|
|
285
|
+
castrum_multipart_parse_packed: {
|
|
286
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
287
|
+
returns: U64_FAST,
|
|
288
|
+
},
|
|
289
|
+
castrum_form_parse_packed: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
290
|
+
// ── Phase 3 — stateless napi-only scalars (now FFI) ──────────
|
|
291
|
+
// Structural parse: parses ONCE, emits a packed token stream with a
|
|
292
|
+
// deduped string table so JS assembles the value with NO second parse.
|
|
293
|
+
castrum_json_parse_packed: {
|
|
294
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
295
|
+
returns: U64_FAST,
|
|
296
|
+
},
|
|
297
|
+
castrum_parse_media_type: {
|
|
298
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
299
|
+
returns: U64_FAST,
|
|
300
|
+
},
|
|
301
|
+
castrum_parse_http_date: {
|
|
302
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
303
|
+
returns: U64_FAST,
|
|
304
|
+
},
|
|
305
|
+
castrum_parse_accept_encoding: {
|
|
306
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
307
|
+
returns: U64_FAST,
|
|
308
|
+
},
|
|
309
|
+
castrum_url_encode_query: { args: abi(['ptr', 'usize']), returns: 'cstring' },
|
|
310
|
+
castrum_url_resolve: {
|
|
311
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize']),
|
|
312
|
+
returns: 'cstring',
|
|
313
|
+
},
|
|
314
|
+
castrum_url_builder_resolve: {
|
|
315
|
+
args: abi(['usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
316
|
+
returns: U64_FAST,
|
|
317
|
+
},
|
|
318
|
+
castrum_mime_from_extension: { args: ['cstring'], returns: 'cstring' },
|
|
319
|
+
castrum_jwt_verify: {
|
|
320
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'i64']),
|
|
321
|
+
returns: 'cstring',
|
|
322
|
+
},
|
|
323
|
+
castrum_ingress_handle_packed: {
|
|
324
|
+
args: abi(['usize', 'ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
325
|
+
returns: U64_FAST,
|
|
326
|
+
},
|
|
327
|
+
// Raw-components sibling of `castrum_ingress_handle_packed`: `url`/`ip`
|
|
328
|
+
// are `cstring` ARGs (Bun transcodes the JS strings to call-scoped
|
|
329
|
+
// NUL-terminated buffers in-engine — no JS-side Buffer.write encode);
|
|
330
|
+
// rid/headers/body are `(ptr,len)` byte slices; out is `(ptr,len)`.
|
|
331
|
+
castrum_ingress_handle_components: {
|
|
332
|
+
args: abi([
|
|
333
|
+
'usize',
|
|
334
|
+
'u8',
|
|
335
|
+
'cstring',
|
|
336
|
+
'cstring',
|
|
337
|
+
'ptr',
|
|
338
|
+
'usize',
|
|
339
|
+
'ptr',
|
|
340
|
+
'usize',
|
|
341
|
+
'ptr',
|
|
342
|
+
'usize',
|
|
343
|
+
'ptr',
|
|
344
|
+
'usize',
|
|
345
|
+
]),
|
|
346
|
+
returns: U64_FAST,
|
|
347
|
+
},
|
|
348
|
+
castrum_ingress_layout: { args: abi(['ptr', 'usize']), returns: U64_FAST },
|
|
349
|
+
// Per-route native stack (`@ignex/native` also dlopens these exact names
|
|
350
|
+
// itself; castrum binds them for the bind-time self-test + parity guard).
|
|
351
|
+
castrum_route_compile: { args: abi(['ptr', 'usize']), returns: U64_FAST },
|
|
352
|
+
castrum_route_run: {
|
|
353
|
+
args: abi(['usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
354
|
+
returns: U64_FAST,
|
|
355
|
+
},
|
|
356
|
+
castrum_route_destroy: { args: ['usize'], returns: 'void' },
|
|
357
|
+
// ── Batch fixed-width hex validation (ObjectId-shaped ids, etc.) ──
|
|
358
|
+
// NEWLINE-separated lines in; one verdict byte (1/0) per line out.
|
|
359
|
+
castrum_hex_validate_batch: {
|
|
360
|
+
args: abi(['ptr', 'usize', 'u32', 'ptr', 'usize']),
|
|
361
|
+
returns: U64_FAST,
|
|
362
|
+
},
|
|
363
|
+
// ── JS-RegExp metacharacter escaping (needed-size convention) ──
|
|
364
|
+
castrum_regex_escape: { args: abi(['ptr', 'usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
365
|
+
// ── Metrics registry (`castrum_metrics_*`) ──────────────────────
|
|
366
|
+
// Caller-owned handle (route-stack ownership model): create returns a
|
|
367
|
+
// Box<Registry> as usize; declare fns take name/label-keys as cstring
|
|
368
|
+
// ARGS (label keys `\x1f`-separated) and return the u32 series id
|
|
369
|
+
// (0xFFFFFFFF = error); record/set cross the packed label VALUES as a
|
|
370
|
+
// `(ptr,len)` pair + the amount as f64.
|
|
371
|
+
castrum_metrics_create: { args: [], returns: U64_FAST },
|
|
372
|
+
castrum_metrics_counter: { args: ['usize', 'cstring', 'cstring'], returns: 'u32' },
|
|
373
|
+
castrum_metrics_gauge: { args: ['usize', 'cstring', 'cstring'], returns: 'u32' },
|
|
374
|
+
castrum_metrics_histogram: {
|
|
375
|
+
args: ['usize', 'cstring', 'cstring', 'cstring'],
|
|
376
|
+
returns: 'u32',
|
|
377
|
+
},
|
|
378
|
+
castrum_metrics_record: {
|
|
379
|
+
args: abi(['usize', 'u32', 'ptr', 'usize', 'f64']),
|
|
380
|
+
returns: 'u8',
|
|
381
|
+
},
|
|
382
|
+
castrum_metrics_gauge_set: {
|
|
383
|
+
args: abi(['usize', 'u32', 'ptr', 'usize', 'f64']),
|
|
384
|
+
returns: 'u8',
|
|
385
|
+
},
|
|
386
|
+
// Zero-encode siblings: the joined label values cross as ONE `cstring`
|
|
387
|
+
// ARG — the engine transcodes the JS string in-engine (no TextEncoder).
|
|
388
|
+
castrum_metrics_record_str: {
|
|
389
|
+
args: ['usize', 'u32', 'cstring', 'f64'],
|
|
390
|
+
returns: 'u8',
|
|
391
|
+
},
|
|
392
|
+
castrum_metrics_gauge_set_str: {
|
|
393
|
+
args: ['usize', 'u32', 'cstring', 'f64'],
|
|
394
|
+
returns: 'u8',
|
|
395
|
+
},
|
|
396
|
+
// Zero-copy text escape: cstring ARG in, cstring return out — the JS
|
|
397
|
+
// side does zero encode AND zero decode (engine clones at return).
|
|
398
|
+
castrum_regex_escape_str: { args: ['cstring'], returns: 'cstring' },
|
|
399
|
+
// String-input batch hex validation: `ids.join('\n')` crosses as a
|
|
400
|
+
// cstring ARG.
|
|
401
|
+
castrum_hex_validate_batch_str: {
|
|
402
|
+
args: abi(['cstring', 'u32', 'ptr', 'usize']),
|
|
403
|
+
returns: U64_FAST,
|
|
404
|
+
},
|
|
405
|
+
castrum_metrics_render: { args: abi(['usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
406
|
+
castrum_metrics_snapshot: { args: abi(['usize', 'ptr', 'usize']), returns: U64_FAST },
|
|
407
|
+
castrum_metrics_record_batch: {
|
|
408
|
+
args: abi(['usize', 'ptr', 'usize']),
|
|
409
|
+
returns: 'u8',
|
|
410
|
+
},
|
|
411
|
+
// Fused wire-level validation: RAW query/cookie → JSON → draft-07 gate.
|
|
412
|
+
// `inner` is the SchemaValidator inner handle; strings cross as cstring
|
|
413
|
+
// ARGs (engine-transcoded).
|
|
414
|
+
castrum_query_validate: { args: ['usize', 'cstring'], returns: 'u8' },
|
|
415
|
+
castrum_cookie_validate: { args: ['usize', 'cstring'], returns: 'u8' },
|
|
416
|
+
// Fused session envelope: seal builds+signs; open verifies + extracts.
|
|
417
|
+
castrum_session_seal: {
|
|
418
|
+
args: ['cstring', 'cstring', 'i64', 'cstring'],
|
|
419
|
+
returns: 'cstring',
|
|
420
|
+
},
|
|
421
|
+
castrum_session_open: {
|
|
422
|
+
args: abi(['cstring', 'cstring', 'ptr', 'usize']),
|
|
423
|
+
returns: U64_FAST,
|
|
424
|
+
},
|
|
425
|
+
// Byte-arg siblings of the pair above: identical core and wire format,
|
|
426
|
+
// but every INPUT crosses as `(ptr,len)`. A `cstring` ARG costs the
|
|
427
|
+
// engine a transcode per arg AND silently truncates at an embedded
|
|
428
|
+
// U+0000 — reachable here because `id`/`data_json` are user-derived.
|
|
429
|
+
castrum_session_seal_bytes: {
|
|
430
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'i64', 'ptr', 'usize']),
|
|
431
|
+
returns: 'cstring',
|
|
432
|
+
},
|
|
433
|
+
castrum_session_open_bytes: {
|
|
434
|
+
args: abi(['ptr', 'usize', 'ptr', 'usize', 'ptr', 'usize']),
|
|
435
|
+
returns: U64_FAST,
|
|
436
|
+
},
|
|
437
|
+
// Off-thread task runtime: `submit` takes a packed `(ptr,len)` arg blob
|
|
438
|
+
// plus a JS-assigned id; `drain` writes the packed completion batch into
|
|
439
|
+
// a caller buffer (needed-size convention). `set_doorbell` takes the raw
|
|
440
|
+
// `JSCallback({threadsafe:true}).ptr` as a bare pointer (`abi` leaves a
|
|
441
|
+
// lone `ptr` untouched), so the pool can wake JS once per batch.
|
|
442
|
+
castrum_task_init: { args: ['u32'], returns: 'u32' },
|
|
443
|
+
castrum_task_submit: { args: abi(['u32', 'ptr', 'usize', 'usize']), returns: 'u32' },
|
|
444
|
+
castrum_task_submit_slice: {
|
|
445
|
+
args: abi(['u32', 'ptr', 'usize', 'ptr', 'usize', 'usize']),
|
|
446
|
+
returns: 'u32',
|
|
447
|
+
},
|
|
448
|
+
castrum_task_submit_out: {
|
|
449
|
+
args: abi(['u32', 'ptr', 'usize', 'usize', 'ptr', 'usize']),
|
|
450
|
+
returns: 'u32',
|
|
451
|
+
},
|
|
452
|
+
castrum_task_drain: { args: abi(['ptr', 'usize']), returns: U64_FAST },
|
|
453
|
+
castrum_task_pending: { args: [], returns: 'u32' },
|
|
454
|
+
castrum_task_cancel: { args: ['usize'], returns: 'u32' },
|
|
455
|
+
castrum_task_set_doorbell: { args: ['ptr'], returns: 'u32' },
|
|
456
|
+
castrum_task_shutdown: { args: [], returns: 'u32' },
|
|
457
|
+
castrum_task_threads: { args: [], returns: 'u32' },
|
|
458
|
+
castrum_metrics_destroy: { args: ['usize'], returns: 'void' },
|
|
459
|
+
}
|
|
460
|
+
}
|