castrum 0.9.3 → 0.9.4

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 (184) hide show
  1. package/Cargo.lock +2259 -0
  2. package/Cargo.toml +138 -0
  3. package/README.md +6 -0
  4. package/build.rs +5 -0
  5. package/castrum.linux-x64-gnu.node +0 -0
  6. package/dist/index.d.ts +1348 -1295
  7. package/dist/index.js +4307 -3776
  8. package/docs/API.md +1 -1
  9. package/docs/ARCHITECTURE.md +3 -2
  10. package/docs/CASE_STUDY.md +2 -2
  11. package/docs/ENVIRONMENT.md +16 -0
  12. package/docs/FFI_BUN_GUIDE.md +89 -6
  13. package/docs/REPO_MAP.md +2 -2
  14. package/index.ts +19 -25
  15. package/package.json +9 -1
  16. package/rust/crypto/aead.rs +378 -0
  17. package/rust/crypto/argon2.rs +265 -0
  18. package/rust/crypto/base64.rs +420 -0
  19. package/rust/crypto/bcrypt.rs +89 -0
  20. package/rust/crypto/cookie_sign.rs +169 -0
  21. package/rust/crypto/csrf.rs +106 -0
  22. package/rust/crypto/ed25519.rs +215 -0
  23. package/rust/crypto/hashing.rs +140 -0
  24. package/rust/crypto/hmac_sha256.rs +88 -0
  25. package/rust/crypto/jwt/api.rs +286 -0
  26. package/rust/crypto/jwt/mod.rs +22 -0
  27. package/rust/crypto/jwt/tests.rs +312 -0
  28. package/rust/crypto/jwt/token.rs +360 -0
  29. package/rust/crypto/mod.rs +27 -0
  30. package/rust/crypto/pbkdf2.rs +96 -0
  31. package/rust/crypto/random_token.rs +85 -0
  32. package/rust/crypto/session.rs +222 -0
  33. package/rust/ffi/crypto.rs +708 -0
  34. package/rust/ffi/hashing.rs +285 -0
  35. package/rust/ffi/http.rs +752 -0
  36. package/rust/ffi/ingress.rs +303 -0
  37. package/rust/ffi/json.rs +517 -0
  38. package/rust/ffi/jwt.rs +419 -0
  39. package/rust/ffi/metrics.rs +373 -0
  40. package/rust/ffi/mod.rs +68 -0
  41. package/rust/ffi/payload.rs +275 -0
  42. package/rust/ffi/probe.rs +60 -0
  43. package/rust/ffi/rate_limit.rs +77 -0
  44. package/rust/ffi/route.rs +85 -0
  45. package/rust/ffi/tests.rs +2333 -0
  46. package/rust/ffi/util.rs +152 -0
  47. package/rust/ffi/validators.rs +123 -0
  48. package/rust/http/accept.rs +606 -0
  49. package/rust/http/cookie_parser.rs +140 -0
  50. package/rust/http/etag.rs +289 -0
  51. package/rust/http/form.rs +132 -0
  52. package/rust/http/headers.rs +267 -0
  53. package/rust/http/http_date.rs +286 -0
  54. package/rust/http/http_parser.rs +252 -0
  55. package/rust/http/media_type.rs +355 -0
  56. package/rust/http/method.rs +200 -0
  57. package/rust/http/mime_lookup.rs +130 -0
  58. package/rust/http/mod.rs +32 -0
  59. package/rust/http/multipart.rs +685 -0
  60. package/rust/http/query_parser.rs +180 -0
  61. package/rust/http/url_codec.rs +296 -0
  62. package/rust/http/url_join.rs +324 -0
  63. package/rust/ingress/api.rs +355 -0
  64. package/rust/ingress/cors.rs +403 -0
  65. package/rust/ingress/ingress_constants.rs +209 -0
  66. package/rust/ingress/ip_trust.rs +312 -0
  67. package/rust/ingress/mod.rs +48 -0
  68. package/rust/ingress/native_route.rs +779 -0
  69. package/rust/ingress/options.rs +176 -0
  70. package/rust/ingress/output.rs +406 -0
  71. package/rust/ingress/packed.rs +191 -0
  72. package/rust/ingress/pipeline.rs +534 -0
  73. package/rust/ingress/proxy.rs +138 -0
  74. package/rust/ingress/rate_limit.rs +538 -0
  75. package/rust/ingress/terminal.rs +114 -0
  76. package/rust/ingress/tests.rs +566 -0
  77. package/rust/ingress/time.rs +70 -0
  78. package/rust/json/fast_schema/capture.rs +336 -0
  79. package/rust/json/fast_schema/compile.rs +556 -0
  80. package/rust/json/fast_schema/cursor.rs +368 -0
  81. package/rust/json/fast_schema/email.rs +282 -0
  82. package/rust/json/fast_schema/errors.rs +247 -0
  83. package/rust/json/fast_schema/mod.rs +57 -0
  84. package/rust/json/fast_schema/tests.rs +1143 -0
  85. package/rust/json/fast_schema/types.rs +140 -0
  86. package/rust/json/fast_schema/validate.rs +931 -0
  87. package/rust/json/json_ops.rs +121 -0
  88. package/rust/json/json_schema.rs +837 -0
  89. package/rust/json/json_ser.rs +881 -0
  90. package/rust/json/mod.rs +19 -0
  91. package/rust/json/napi_marshal.rs +318 -0
  92. package/rust/json/patch/api.rs +60 -0
  93. package/rust/json/patch/engine.rs +153 -0
  94. package/rust/json/patch/mod.rs +47 -0
  95. package/rust/json/patch/ops.rs +270 -0
  96. package/rust/json/patch/pointer.rs +49 -0
  97. package/rust/json/patch/tests.rs +499 -0
  98. package/rust/lib.rs +72 -0
  99. package/rust/metrics/api.rs +148 -0
  100. package/rust/metrics/mod.rs +21 -0
  101. package/rust/metrics/registry.rs +961 -0
  102. package/rust/panic_safety.rs +40 -0
  103. package/rust/payload/compress.rs +512 -0
  104. package/rust/payload/mod.rs +13 -0
  105. package/rust/payload/sse.rs +214 -0
  106. package/rust/payload/template.rs +253 -0
  107. package/rust/payload/websocket.rs +69 -0
  108. package/rust/payload/ws_frames.rs +354 -0
  109. package/rust/proptest_suite.rs +139 -0
  110. package/rust/selection.rs +95 -0
  111. package/rust/src/bin/native-bench.rs +495 -0
  112. package/rust/test_support.rs +71 -0
  113. package/rust/util/batch/api.rs +266 -0
  114. package/rust/util/batch/core.rs +473 -0
  115. package/rust/util/batch/mod.rs +23 -0
  116. package/rust/util/batch/tests.rs +445 -0
  117. package/rust/util/batch_core.rs +184 -0
  118. package/rust/util/bytes.rs +407 -0
  119. package/rust/util/mod.rs +34 -0
  120. package/rust/util/packed.rs +473 -0
  121. package/rust/util/text.rs +130 -0
  122. package/rust/util/threadpool.rs +128 -0
  123. package/rust/util/validation.rs +419 -0
  124. package/rust-toolchain.toml +17 -0
  125. package/scripts/postinstall.mjs +199 -0
  126. package/src/ingress/constants.ts +1 -1
  127. package/src/ingress/context.ts +2 -2
  128. package/src/ingress/decode/baked-result.ts +13 -13
  129. package/src/ingress/decode/fast-result.ts +17 -17
  130. package/src/ingress/errors.ts +7 -7
  131. package/src/ingress/headers/baked-templates.ts +1 -1
  132. package/src/ingress/headers/fast-templates.ts +3 -3
  133. package/src/ingress/index.ts +1 -1
  134. package/src/ingress/native-route.ts +9 -5
  135. package/src/ingress/options.ts +1 -1
  136. package/src/ingress/packing/gather-raw-headers.ts +1 -1
  137. package/src/ingress/packing/route-wire.ts +1 -5
  138. package/src/ingress/response/error-bodies.ts +5 -5
  139. package/src/ingress/response/terminal.ts +1 -1
  140. package/src/ingress/routes/echo.ts +4 -4
  141. package/src/ingress/routes/fallback.ts +2 -2
  142. package/src/ingress/routes/head.ts +2 -2
  143. package/src/ingress/routes/native.ts +2 -1
  144. package/src/ingress/routes/options.ts +1 -1
  145. package/src/ingress/routes/read.ts +2 -2
  146. package/src/ingress/server.ts +10 -6
  147. package/src/ingress/status.ts +7 -7
  148. package/src/integration/batch.ts +1 -1
  149. package/src/integration/index.ts +9 -9
  150. package/src/integration/pipeline.ts +6 -6
  151. package/src/integration/websocket.ts +2 -2
  152. package/src/loader/index.ts +1 -5
  153. package/src/loader/ops.ts +1 -1
  154. package/src/native/ffi/build/codecs.ts +104 -8
  155. package/src/native/ffi/build/compress.ts +14 -10
  156. package/src/native/ffi/build/instances.ts +40 -3
  157. package/src/native/ffi/build/metrics.ts +224 -0
  158. package/src/native/ffi/build/parse.ts +59 -23
  159. package/src/native/ffi/build/util.ts +14 -1
  160. package/src/native/ffi/build.ts +2 -0
  161. package/src/native/ffi/selftest.ts +10 -3
  162. package/src/native/ffi/types.ts +111 -9
  163. package/src/native/ffi.ts +87 -1
  164. package/src/native/index.ts +1 -1
  165. package/src/native/loader.ts +2 -2
  166. package/src/native/types/instances.ts +34 -0
  167. package/src/native/types.ts +11 -0
  168. package/src/runtime/codec.ts +53 -9
  169. package/src/runtime/types.ts +7 -0
  170. package/src/rust-ffi/batch/build.ts +16 -9
  171. package/src/rust-ffi/index.ts +7 -6
  172. package/src/rust-ffi/options.ts +1 -3
  173. package/src/rust-ffi/proven.ts +3 -3
  174. package/src/rust-ffi/scalar/crypto.ts +1 -1
  175. package/src/rust-ffi/scalar/factories.ts +73 -0
  176. package/src/rust-ffi/scalar/hashing.ts +1 -1
  177. package/src/rust-ffi/scalar/http.ts +17 -11
  178. package/src/rust-ffi/scalar/index.ts +2 -0
  179. package/src/rust-ffi/scalar/interface.ts +20 -0
  180. package/src/rust-ffi/scalar/payload.ts +11 -3
  181. package/src/rust-ffi/scalar/validation.ts +121 -0
  182. package/src/selection.ts +1 -1
  183. package/src/shared/bytes.ts +5 -4
  184. package/src/shared/codec.ts +18 -3
@@ -0,0 +1,285 @@
1
+ // rust/ffi/hashing.rs — hashing + scalar codec C-ABI exports.
2
+ //
3
+ // crc32 / fnv1a64 / xxh3 checksums, JSON/UTF-8 validity, hex + percent-encode/
4
+ // decode, and the `id`-sum aggregation. All stateless `&[u8]` → scalar / write
5
+ // into a caller buffer.
6
+
7
+ use std::ptr;
8
+ use std::slice;
9
+
10
+ /// CRC32 over `data[0..len]`. Returns the CRC-32 checksum.
11
+ ///
12
+ /// # Safety
13
+ /// `data` must be valid for reads of `len` bytes.
14
+ #[no_mangle]
15
+ pub unsafe extern "C" fn castrum_crc32(data: *const u8, len: usize) -> u32 {
16
+ if data.is_null() && len != 0 {
17
+ return 0;
18
+ }
19
+ let bytes = slice::from_raw_parts(data, len);
20
+ crate::crypto::hashing::crc32_bytes(bytes)
21
+ }
22
+
23
+ /// FNV-1a 64 over `data[0..len]`.
24
+ ///
25
+ /// # Safety
26
+ /// `data` must be valid for reads of `len` bytes.
27
+ #[no_mangle]
28
+ pub unsafe extern "C" fn castrum_fnv1a64(data: *const u8, len: usize) -> u64 {
29
+ if data.is_null() && len != 0 {
30
+ return 0;
31
+ }
32
+ let bytes = slice::from_raw_parts(data, len);
33
+ crate::crypto::hashing::fnv1a64_bytes(bytes)
34
+ }
35
+
36
+ /// XXH3-64 over `data[0..len]`.
37
+ ///
38
+ /// # Safety
39
+ /// `data` must be valid for reads of `len` bytes.
40
+ #[no_mangle]
41
+ pub unsafe extern "C" fn castrum_xxh3(data: *const u8, len: usize) -> u64 {
42
+ if data.is_null() && len != 0 {
43
+ return 0;
44
+ }
45
+ let bytes = slice::from_raw_parts(data, len);
46
+ crate::crypto::hashing::fast_hash_bytes(bytes)
47
+ }
48
+
49
+ /// JSON-validity check over `data[0..len]`. Returns 1 if the bytes are
50
+ /// well-formed JSON, 0 otherwise.
51
+ ///
52
+ /// # Safety
53
+ /// `data` must be valid for reads of `len` bytes.
54
+ #[no_mangle]
55
+ pub unsafe extern "C" fn castrum_json_valid(data: *const u8, len: usize) -> u8 {
56
+ if data.is_null() {
57
+ return 0;
58
+ }
59
+ let bytes = slice::from_raw_parts(data, len);
60
+ u8::from(crate::json::json_ops::json_valid_bytes(bytes))
61
+ }
62
+
63
+ /// UTF-8 validity check over `data[0..len]`. Returns 1 if the bytes are valid
64
+ /// UTF-8, 0 otherwise. Used by the JS `urlDecode` wrapper to mirror the napi
65
+ /// fatal UTF-8 validation without a `TextDecoder` on the Bun path.
66
+ ///
67
+ /// # Safety
68
+ /// `data` must be valid for reads of `len` bytes.
69
+ #[no_mangle]
70
+ pub unsafe extern "C" fn castrum_utf8_valid(data: *const u8, len: usize) -> u8 {
71
+ if data.is_null() {
72
+ return 0;
73
+ }
74
+ let bytes = slice::from_raw_parts(data, len);
75
+ u8::from(std::str::from_utf8(bytes).is_ok())
76
+ }
77
+
78
+ /// Lowercase-hex encode `data[0..len]` into `out[0..out_cap]`. Returns bytes
79
+ /// written (`len * 2`), or 0 if `out_cap < len * 2`.
80
+ ///
81
+ /// # Safety
82
+ /// `data` must be valid for reads of `len` bytes; `out` must be valid for
83
+ /// writes of up to `out_cap` bytes.
84
+ #[no_mangle]
85
+ pub unsafe extern "C" fn castrum_hex_encode(
86
+ data: *const u8,
87
+ len: usize,
88
+ out: *mut u8,
89
+ out_cap: usize,
90
+ ) -> usize {
91
+ if data.is_null() || out.is_null() || len.checked_mul(2).is_none_or(|n| n > out_cap) {
92
+ return 0;
93
+ }
94
+ let input = slice::from_raw_parts(data, len);
95
+ let output = slice::from_raw_parts_mut(out, len * 2);
96
+ crate::crypto::base64::hex_encode_into_slice(input, output).unwrap_or(0)
97
+ }
98
+
99
+ /// RFC 3986 percent-encode `data[0..len]` into `out[0..out_cap]`. Returns
100
+ /// bytes written, or 0 if the output buffer is too small (callers can size
101
+ /// `len * 3` to guarantee success).
102
+ ///
103
+ /// # Safety
104
+ /// `data` must be valid for reads of `len` bytes; `out` must be valid for
105
+ /// writes of up to `out_cap` bytes.
106
+ #[no_mangle]
107
+ pub unsafe extern "C" fn castrum_url_encode(
108
+ data: *const u8,
109
+ len: usize,
110
+ out: *mut u8,
111
+ out_cap: usize,
112
+ ) -> usize {
113
+ if data.is_null() || out.is_null() {
114
+ return 0;
115
+ }
116
+ let input = slice::from_raw_parts(data, len);
117
+ let output = slice::from_raw_parts_mut(out, out_cap);
118
+ crate::http::url_codec::url_encode_into_slice(input, output).unwrap_or_default()
119
+ }
120
+
121
+ /// Hex-decode `data[0..len]` into `out[0..out_cap]`. Returns bytes written
122
+ /// (`len / 2`), or 0 on odd length / invalid hex digit / too-small buffer.
123
+ ///
124
+ /// # Safety
125
+ /// `data` must be valid for reads of `len` bytes; `out` must be valid for
126
+ /// writes of up to `out_cap` bytes.
127
+ #[no_mangle]
128
+ pub unsafe extern "C" fn castrum_hex_decode(
129
+ data: *const u8,
130
+ len: usize,
131
+ out: *mut u8,
132
+ out_cap: usize,
133
+ ) -> usize {
134
+ if data.is_null() || out.is_null() {
135
+ return 0;
136
+ }
137
+ let input = slice::from_raw_parts(data, len);
138
+ let output = slice::from_raw_parts_mut(out, out_cap);
139
+ crate::crypto::base64::hex_decode_into_slice(input, output).unwrap_or_default()
140
+ }
141
+
142
+ /// Percent-decode `data[0..len]` into `out[0..out_cap]`. Returns bytes
143
+ /// written, or 0 on malformed `%XX` / too-small buffer.
144
+ ///
145
+ /// # Safety
146
+ /// `data` must be valid for reads of `len` bytes; `out` must be valid for
147
+ /// writes of up to `out_cap` bytes.
148
+ #[no_mangle]
149
+ pub unsafe extern "C" fn castrum_url_decode(
150
+ data: *const u8,
151
+ len: usize,
152
+ out: *mut u8,
153
+ out_cap: usize,
154
+ ) -> usize {
155
+ if data.is_null() || out.is_null() {
156
+ return 0;
157
+ }
158
+ let input = slice::from_raw_parts(data, len);
159
+ let output = slice::from_raw_parts_mut(out, out_cap);
160
+ crate::http::url_codec::url_decode_into_slice(input, output).unwrap_or_default()
161
+ }
162
+
163
+ /// Sum of `id` fields across a JSON array (sonic-rs zero-DOM) → packed
164
+ /// `[u8 ok][i64 sum LE]` output (9 B: 1 = valid array — the sum may be 0 —,
165
+ /// 0 = invalid; return 9/1/0 bytes). The ok byte removes the old scalar-i64
166
+ /// ambiguity (0 for both a legit zero-sum and invalid input) that forced the
167
+ /// JS builder to re-dispatch to napi on every 0n.
168
+ ///
169
+ /// # Safety
170
+ /// `data` must be valid for reads of `len` bytes; `out` must be valid for
171
+ /// writes of `out_len` bytes.
172
+ #[no_mangle]
173
+ pub unsafe extern "C" fn castrum_json_sum_ids(
174
+ data: *const u8,
175
+ len: usize,
176
+ out: *mut u8,
177
+ out_len: usize,
178
+ ) -> usize {
179
+ if data.is_null() || out.is_null() {
180
+ return 0;
181
+ }
182
+ match crate::json::json_ops::json_sum_ids_bytes(slice::from_raw_parts(data, len)) {
183
+ Ok(sum) if out_len >= 9 => {
184
+ *out = 1;
185
+ ptr::copy_nonoverlapping(sum.to_le_bytes().as_ptr(), out.add(1), 8);
186
+ 9
187
+ }
188
+ Ok(_) => 9, // too-small buffer → exact required size (growExact)
189
+ Err(_) if out_len >= 1 => {
190
+ *out = 0;
191
+ 1
192
+ }
193
+ Err(_) => 1, // too-small buffer → exact required size
194
+ }
195
+ }
196
+
197
+ /// Escape JS-RegExp metacharacters (`\ . * + ? ^ $ { } | ( ) [ ]`) in
198
+ /// `data[0..len]` into `out[0..out_cap]` — the "match untrusted input
199
+ /// literally" utility. Needed-size convention: `0` = null pointers (real
200
+ /// error); `w > out_cap` = exact required size; else bytes written.
201
+ ///
202
+ /// # Safety
203
+ /// `data` must be valid for reads of `len` bytes; `out` for writes up to
204
+ /// `out_cap`.
205
+ #[no_mangle]
206
+ pub unsafe extern "C" fn castrum_regex_escape(
207
+ data: *const u8,
208
+ len: usize,
209
+ out: *mut u8,
210
+ out_cap: usize,
211
+ ) -> usize {
212
+ if data.is_null() || out.is_null() {
213
+ return 0;
214
+ }
215
+ let input = slice::from_raw_parts(data, len);
216
+ let needed = crate::util::text::regex_escape_len(input);
217
+ if needed > out_cap {
218
+ return needed;
219
+ }
220
+ crate::util::text::regex_escape_write(input, slice::from_raw_parts_mut(out, out_cap));
221
+ needed
222
+ }
223
+
224
+ /// String-input / string-return sibling of [`castrum_regex_escape`]: the
225
+ /// engine transcodes the JS string to a call-scoped NUL-terminated UTF-8
226
+ /// buffer on the way in (ZERO JS-side encode) and clones the escaped result
227
+ /// out of the per-thread `CSTR_BUF` on the way out (ZERO JS-side decode).
228
+ /// This is the full Bun-1.4 zero-copy text path — the whole call is the FFI
229
+ /// crossing + the escape itself. Input containing NUL is escaped only up to
230
+ /// the NUL (text needles never contain one). `null` = panic guard fallback.
231
+ ///
232
+ /// # Safety
233
+ /// `input` must be a valid NUL-terminated C string.
234
+ #[no_mangle]
235
+ pub unsafe extern "C" fn castrum_regex_escape_str(
236
+ input: *const std::os::raw::c_char,
237
+ ) -> *const std::os::raw::c_char {
238
+ if input.is_null() {
239
+ return std::ptr::null();
240
+ }
241
+ let bytes = std::ffi::CStr::from_ptr(input).to_bytes();
242
+ let needed = crate::util::text::regex_escape_len(bytes);
243
+ super::util::panic_guard(
244
+ || {
245
+ // Escaping a valid UTF-8 slice cannot fail — always Some(written).
246
+ super::util::cstring_return(needed, |buf| {
247
+ Some(crate::util::text::regex_escape_write(bytes, buf))
248
+ })
249
+ },
250
+ std::ptr::null(),
251
+ )
252
+ }
253
+
254
+ /// String-input sibling of [`crate::ffi::validators::castrum_hex_validate_batch`]:
255
+ /// the NEWLINE-separated ids cross as ONE `cstring` ARG (the engine
256
+ /// transcodes the JS `ids.join('\n')` in-engine — zero JS-side encode for the
257
+ /// common "array of id strings" caller). Needed-size convention as usual.
258
+ ///
259
+ /// # Safety
260
+ /// `ids` must be a valid NUL-terminated C string; `out` for writes up to
261
+ /// `out_cap`.
262
+ #[no_mangle]
263
+ pub unsafe extern "C" fn castrum_hex_validate_batch_str(
264
+ ids: *const std::os::raw::c_char,
265
+ width: u32,
266
+ out: *mut u8,
267
+ out_cap: usize,
268
+ ) -> usize {
269
+ if ids.is_null() || out.is_null() {
270
+ return 0;
271
+ }
272
+ let input = std::ffi::CStr::from_ptr(ids).to_bytes();
273
+ let needed = crate::util::validation::hex_batch_count(input);
274
+ if needed > out_cap {
275
+ return needed;
276
+ }
277
+ let mut buf = Vec::with_capacity(needed);
278
+ match crate::util::validation::hex_batch_valid_into(input, width as usize, &mut buf) {
279
+ Ok(()) if buf.len() <= out_cap => {
280
+ slice::from_raw_parts_mut(out, buf.len()).copy_from_slice(&buf);
281
+ buf.len()
282
+ }
283
+ _ => 0,
284
+ }
285
+ }