castrum 0.9.2 → 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.
- package/Cargo.lock +2259 -0
- package/Cargo.toml +138 -0
- package/README.md +6 -0
- package/build.rs +5 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +1348 -1295
- package/dist/index.js +4307 -3776
- package/docs/API.md +1 -1
- package/docs/ARCHITECTURE.md +3 -2
- package/docs/CASE_STUDY.md +2 -2
- package/docs/ENVIRONMENT.md +16 -0
- package/docs/FFI_BUN_GUIDE.md +89 -6
- package/docs/REPO_MAP.md +2 -2
- package/index.ts +19 -25
- package/package.json +9 -1
- package/rust/crypto/aead.rs +378 -0
- package/rust/crypto/argon2.rs +265 -0
- package/rust/crypto/base64.rs +420 -0
- package/rust/crypto/bcrypt.rs +89 -0
- package/rust/crypto/cookie_sign.rs +169 -0
- package/rust/crypto/csrf.rs +106 -0
- package/rust/crypto/ed25519.rs +215 -0
- package/rust/crypto/hashing.rs +140 -0
- package/rust/crypto/hmac_sha256.rs +88 -0
- package/rust/crypto/jwt/api.rs +286 -0
- package/rust/crypto/jwt/mod.rs +22 -0
- package/rust/crypto/jwt/tests.rs +312 -0
- package/rust/crypto/jwt/token.rs +360 -0
- package/rust/crypto/mod.rs +27 -0
- package/rust/crypto/pbkdf2.rs +96 -0
- package/rust/crypto/random_token.rs +85 -0
- package/rust/crypto/session.rs +222 -0
- package/rust/ffi/crypto.rs +708 -0
- package/rust/ffi/hashing.rs +285 -0
- package/rust/ffi/http.rs +752 -0
- package/rust/ffi/ingress.rs +303 -0
- package/rust/ffi/json.rs +517 -0
- package/rust/ffi/jwt.rs +419 -0
- package/rust/ffi/metrics.rs +373 -0
- package/rust/ffi/mod.rs +68 -0
- package/rust/ffi/payload.rs +275 -0
- package/rust/ffi/probe.rs +60 -0
- package/rust/ffi/rate_limit.rs +77 -0
- package/rust/ffi/route.rs +85 -0
- package/rust/ffi/tests.rs +2333 -0
- package/rust/ffi/util.rs +152 -0
- package/rust/ffi/validators.rs +123 -0
- package/rust/http/accept.rs +606 -0
- package/rust/http/cookie_parser.rs +140 -0
- package/rust/http/etag.rs +289 -0
- package/rust/http/form.rs +132 -0
- package/rust/http/headers.rs +267 -0
- package/rust/http/http_date.rs +286 -0
- package/rust/http/http_parser.rs +252 -0
- package/rust/http/media_type.rs +355 -0
- package/rust/http/method.rs +200 -0
- package/rust/http/mime_lookup.rs +130 -0
- package/rust/http/mod.rs +32 -0
- package/rust/http/multipart.rs +685 -0
- package/rust/http/query_parser.rs +180 -0
- package/rust/http/url_codec.rs +296 -0
- package/rust/http/url_join.rs +324 -0
- package/rust/ingress/api.rs +355 -0
- package/rust/ingress/cors.rs +403 -0
- package/rust/ingress/ingress_constants.rs +209 -0
- package/rust/ingress/ip_trust.rs +312 -0
- package/rust/ingress/mod.rs +48 -0
- package/rust/ingress/native_route.rs +779 -0
- package/rust/ingress/options.rs +176 -0
- package/rust/ingress/output.rs +406 -0
- package/rust/ingress/packed.rs +191 -0
- package/rust/ingress/pipeline.rs +534 -0
- package/rust/ingress/proxy.rs +138 -0
- package/rust/ingress/rate_limit.rs +538 -0
- package/rust/ingress/terminal.rs +114 -0
- package/rust/ingress/tests.rs +566 -0
- package/rust/ingress/time.rs +70 -0
- package/rust/json/fast_schema/capture.rs +336 -0
- package/rust/json/fast_schema/compile.rs +556 -0
- package/rust/json/fast_schema/cursor.rs +368 -0
- package/rust/json/fast_schema/email.rs +282 -0
- package/rust/json/fast_schema/errors.rs +247 -0
- package/rust/json/fast_schema/mod.rs +57 -0
- package/rust/json/fast_schema/tests.rs +1143 -0
- package/rust/json/fast_schema/types.rs +140 -0
- package/rust/json/fast_schema/validate.rs +931 -0
- package/rust/json/json_ops.rs +121 -0
- package/rust/json/json_schema.rs +837 -0
- package/rust/json/json_ser.rs +881 -0
- package/rust/json/mod.rs +19 -0
- package/rust/json/napi_marshal.rs +318 -0
- package/rust/json/patch/api.rs +60 -0
- package/rust/json/patch/engine.rs +153 -0
- package/rust/json/patch/mod.rs +47 -0
- package/rust/json/patch/ops.rs +270 -0
- package/rust/json/patch/pointer.rs +49 -0
- package/rust/json/patch/tests.rs +499 -0
- package/rust/lib.rs +72 -0
- package/rust/metrics/api.rs +148 -0
- package/rust/metrics/mod.rs +21 -0
- package/rust/metrics/registry.rs +961 -0
- package/rust/panic_safety.rs +40 -0
- package/rust/payload/compress.rs +512 -0
- package/rust/payload/mod.rs +13 -0
- package/rust/payload/sse.rs +214 -0
- package/rust/payload/template.rs +253 -0
- package/rust/payload/websocket.rs +69 -0
- package/rust/payload/ws_frames.rs +354 -0
- package/rust/proptest_suite.rs +139 -0
- package/rust/selection.rs +95 -0
- package/rust/src/bin/native-bench.rs +495 -0
- package/rust/test_support.rs +71 -0
- package/rust/util/batch/api.rs +266 -0
- package/rust/util/batch/core.rs +473 -0
- package/rust/util/batch/mod.rs +23 -0
- package/rust/util/batch/tests.rs +445 -0
- package/rust/util/batch_core.rs +184 -0
- package/rust/util/bytes.rs +407 -0
- package/rust/util/mod.rs +34 -0
- package/rust/util/packed.rs +473 -0
- package/rust/util/text.rs +130 -0
- package/rust/util/threadpool.rs +128 -0
- package/rust/util/validation.rs +419 -0
- package/rust-toolchain.toml +17 -0
- package/scripts/postinstall.mjs +199 -0
- package/src/ingress/constants.ts +1 -1
- package/src/ingress/context.ts +2 -2
- package/src/ingress/decode/baked-result.ts +13 -13
- package/src/ingress/decode/fast-result.ts +17 -17
- package/src/ingress/errors.ts +7 -7
- package/src/ingress/headers/baked-templates.ts +1 -1
- package/src/ingress/headers/fast-templates.ts +3 -3
- package/src/ingress/index.ts +1 -1
- package/src/ingress/native-route.ts +9 -5
- package/src/ingress/options.ts +1 -1
- package/src/ingress/packing/gather-raw-headers.ts +1 -1
- package/src/ingress/packing/route-wire.ts +1 -5
- package/src/ingress/response/error-bodies.ts +5 -5
- package/src/ingress/response/terminal.ts +1 -1
- package/src/ingress/routes/echo.ts +4 -4
- package/src/ingress/routes/fallback.ts +2 -2
- package/src/ingress/routes/head.ts +2 -2
- package/src/ingress/routes/native.ts +2 -1
- package/src/ingress/routes/options.ts +1 -1
- package/src/ingress/routes/read.ts +2 -2
- package/src/ingress/server.ts +10 -6
- package/src/ingress/status.ts +7 -7
- package/src/integration/batch.ts +1 -1
- package/src/integration/index.ts +9 -9
- package/src/integration/pipeline.ts +6 -6
- package/src/integration/websocket.ts +2 -2
- package/src/loader/index.ts +1 -5
- package/src/loader/ops.ts +1 -1
- package/src/native/ffi/build/codecs.ts +104 -8
- package/src/native/ffi/build/compress.ts +14 -10
- package/src/native/ffi/build/instances.ts +40 -3
- package/src/native/ffi/build/metrics.ts +224 -0
- package/src/native/ffi/build/parse.ts +59 -23
- package/src/native/ffi/build/util.ts +14 -1
- package/src/native/ffi/build.ts +2 -0
- package/src/native/ffi/selftest.ts +10 -3
- package/src/native/ffi/types.ts +111 -9
- package/src/native/ffi.ts +87 -1
- package/src/native/index.ts +1 -1
- package/src/native/loader.ts +2 -2
- package/src/native/types/instances.ts +34 -0
- package/src/native/types.ts +11 -0
- package/src/runtime/codec.ts +53 -9
- package/src/runtime/types.ts +7 -0
- package/src/rust-ffi/batch/build.ts +16 -9
- package/src/rust-ffi/index.ts +7 -6
- package/src/rust-ffi/options.ts +1 -3
- package/src/rust-ffi/proven.ts +3 -3
- package/src/rust-ffi/scalar/crypto.ts +1 -1
- package/src/rust-ffi/scalar/factories.ts +73 -0
- package/src/rust-ffi/scalar/hashing.ts +1 -1
- package/src/rust-ffi/scalar/http.ts +17 -11
- package/src/rust-ffi/scalar/index.ts +2 -0
- package/src/rust-ffi/scalar/interface.ts +20 -0
- package/src/rust-ffi/scalar/payload.ts +11 -3
- package/src/rust-ffi/scalar/validation.ts +121 -0
- package/src/selection.ts +1 -1
- package/src/shared/bytes.ts +5 -4
- 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
|
+
}
|