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.
- 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
package/rust/ffi/util.rs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// rust/ffi/util.rs — shared helpers for the C-ABI export surface.
|
|
2
|
+
//
|
|
3
|
+
// The per-thread state + panic containment every `castrum_*` export relies on:
|
|
4
|
+
// compiled-HMAC-key LRU, AEAD algorithm map, `catch_unwind` guard, and the
|
|
5
|
+
// per-thread cstring buffer. Kept together so the domain wrapper files
|
|
6
|
+
// (`crypto.rs`, `jwt.rs`, …) stay focused on their symbols.
|
|
7
|
+
|
|
8
|
+
use aws_lc_rs::aead::{AES_256_GCM, CHACHA20_POLY1305};
|
|
9
|
+
use aws_lc_rs::hmac;
|
|
10
|
+
use lru::LruCache;
|
|
11
|
+
use std::cell::RefCell;
|
|
12
|
+
use std::num::NonZeroUsize;
|
|
13
|
+
use std::slice;
|
|
14
|
+
|
|
15
|
+
/// Build the HMAC-SHA256 key used by the scalar HMAC/cookie/CSRF cores (the
|
|
16
|
+
/// same per-call construction the napi scalar fns use). Test-only reference
|
|
17
|
+
/// helper: the hot C-ABI fns go through `hmac_key_cached` below.
|
|
18
|
+
#[cfg(test)]
|
|
19
|
+
#[inline]
|
|
20
|
+
pub(crate) fn hmac_key(secret: &[u8]) -> hmac::Key {
|
|
21
|
+
hmac::Key::new(hmac::HMAC_SHA256, secret)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Capacity of the per-thread compiled-key LRU.
|
|
25
|
+
pub(crate) const HMAC_KEY_CACHE_CAP: usize = 16;
|
|
26
|
+
|
|
27
|
+
// Test-only hit counter proving the cache actually reuses compiled keys.
|
|
28
|
+
#[cfg(test)]
|
|
29
|
+
thread_local! {
|
|
30
|
+
pub(crate) static HMAC_CACHE_HITS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The cache itself is also test-visible (tests clear it to measure hit counts).
|
|
34
|
+
thread_local! {
|
|
35
|
+
pub(crate) static HMAC_KEY_CACHE: RefCell<LruCache<Vec<u8>, hmac::Key>> = RefCell::new(
|
|
36
|
+
LruCache::new(NonZeroUsize::new(HMAC_KEY_CACHE_CAP).expect("cap is nonzero")),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Return a compiled HMAC-SHA256 key for `secret`, reusing a cached copy when
|
|
41
|
+
/// the same secret was used recently (per-thread LRU, cap 16).
|
|
42
|
+
#[inline]
|
|
43
|
+
pub(crate) fn hmac_key_cached(secret: &[u8]) -> hmac::Key {
|
|
44
|
+
HMAC_KEY_CACHE.with(|cache| {
|
|
45
|
+
let mut cache = cache.borrow_mut();
|
|
46
|
+
if let Some(key) = cache.get(secret) {
|
|
47
|
+
#[cfg(test)]
|
|
48
|
+
HMAC_CACHE_HITS.with(|h| h.set(h.get() + 1));
|
|
49
|
+
return key.clone();
|
|
50
|
+
}
|
|
51
|
+
let key = hmac::Key::new(hmac::HMAC_SHA256, secret);
|
|
52
|
+
cache.put(secret.to_vec(), key.clone());
|
|
53
|
+
key
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Map an algorithm selector byte to the AEAD `Algorithm` (0 = AES-256-GCM,
|
|
58
|
+
/// 1 = ChaCha20-Poly1305) — mirrors `crate::crypto::aead::resolve_algorithm`.
|
|
59
|
+
#[inline]
|
|
60
|
+
pub(crate) fn aead_alg(alg: u8) -> Option<&'static aws_lc_rs::aead::Algorithm> {
|
|
61
|
+
match alg {
|
|
62
|
+
0 => Some(&AES_256_GCM),
|
|
63
|
+
1 => Some(&CHACHA20_POLY1305),
|
|
64
|
+
_ => None,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Run a fallible native core under `catch_unwind`, returning `fallback` on a
|
|
69
|
+
/// panic.
|
|
70
|
+
///
|
|
71
|
+
/// The napi path relies on napi-rs wrapping every call in `catch_unwind` (a
|
|
72
|
+
/// panic becomes a JS exception/500). The raw C ABI (`bun:ffi`) has no such
|
|
73
|
+
/// guard, so a panic unwinding through an `extern "C"` frame is UB and kills
|
|
74
|
+
/// the whole Bun process (this is what happened under burst load in
|
|
75
|
+
/// `11-concurrent-burst`). Every `castrum_*` export that runs a fallible /
|
|
76
|
+
/// allocating core routes through this helper for parity: a panic becomes the
|
|
77
|
+
/// caller's error sentinel (`0` / `None`), never a process crash.
|
|
78
|
+
/// `AssertUnwindSafe` is required because the closures capture `&mut [u8]`
|
|
79
|
+
/// output slices.
|
|
80
|
+
#[inline]
|
|
81
|
+
pub(crate) fn panic_guard<T>(f: impl FnOnce() -> T, fallback: T) -> T {
|
|
82
|
+
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(fallback)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Write the packed `[u8 allowed][u32 remaining LE][i64 reset_ms LE]` verdict;
|
|
86
|
+
/// returns bytes written or the exact required size (13) when `out_cap` is too
|
|
87
|
+
/// small. Never returns 0 on a valid call.
|
|
88
|
+
///
|
|
89
|
+
/// # Safety
|
|
90
|
+
/// `out` must be valid for writes up to `out_cap`.
|
|
91
|
+
pub(crate) unsafe fn write_rate_check(
|
|
92
|
+
allowed: bool,
|
|
93
|
+
remaining: u32,
|
|
94
|
+
reset_ms: i64,
|
|
95
|
+
out: *mut u8,
|
|
96
|
+
out_cap: usize,
|
|
97
|
+
) -> usize {
|
|
98
|
+
if out_cap < 13 {
|
|
99
|
+
return 13;
|
|
100
|
+
}
|
|
101
|
+
let o = slice::from_raw_parts_mut(out, 13);
|
|
102
|
+
o[0] = u8::from(allowed);
|
|
103
|
+
o[1..5].copy_from_slice(&remaining.to_le_bytes());
|
|
104
|
+
o[5..13].copy_from_slice(&reset_ms.to_le_bytes());
|
|
105
|
+
13
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── cstring-returning writers (per-thread reused buffer) ─────────
|
|
109
|
+
// Bun's `cstring` FFI return type has the ENGINE clone the result string out
|
|
110
|
+
// of the returned pointer at the moment of the call (bun:ffi docs), so the JS
|
|
111
|
+
// side pays ZERO decode + ZERO allocation for these single-string outputs.
|
|
112
|
+
// The buffer is thread-local and reused across calls (same pattern as
|
|
113
|
+
// HMAC_KEY_CACHE above) — valid only until the next call on the same thread,
|
|
114
|
+
// which is all the engine needs (it clones synchronously at return).
|
|
115
|
+
thread_local! {
|
|
116
|
+
static CSTR_BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Write `write` into the per-thread buffer sized to `needed` (reused across
|
|
120
|
+
/// calls), null-terminate, and return the pointer. `null` = write error
|
|
121
|
+
/// (malformed input / crypto failure — the caller maps to a throw or sentinel).
|
|
122
|
+
pub(crate) fn cstring_return(
|
|
123
|
+
needed: usize,
|
|
124
|
+
write: impl FnOnce(&mut [u8]) -> Option<usize>,
|
|
125
|
+
) -> *const std::os::raw::c_char {
|
|
126
|
+
CSTR_BUF.with(|cell| {
|
|
127
|
+
let mut buf = cell.borrow_mut();
|
|
128
|
+
if buf.len() < needed + 1 {
|
|
129
|
+
buf.resize(needed + 1, 0);
|
|
130
|
+
}
|
|
131
|
+
let cap = buf.len() - 1;
|
|
132
|
+
match write(&mut buf[..cap]) {
|
|
133
|
+
Some(w) => {
|
|
134
|
+
buf[w] = 0;
|
|
135
|
+
buf.as_ptr() as *const std::os::raw::c_char
|
|
136
|
+
}
|
|
137
|
+
None => std::ptr::null(),
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Read the bytes of a `cstring`-returning FFI symbol's result. The engine
|
|
143
|
+
/// cloned the string at the call, so the per-thread buffer reuse is safe here.
|
|
144
|
+
/// Test-only: the sole production consumer was the removed task-group dispatch
|
|
145
|
+
/// (`run_one_task`); remaining callers are in the C-ABI unit tests below.
|
|
146
|
+
#[cfg(test)]
|
|
147
|
+
pub(crate) unsafe fn cstr_bytes(p: *const std::os::raw::c_char) -> Option<Vec<u8>> {
|
|
148
|
+
if p.is_null() {
|
|
149
|
+
return None;
|
|
150
|
+
}
|
|
151
|
+
Some(std::ffi::CStr::from_ptr(p).to_bytes().to_vec())
|
|
152
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// rust/ffi/validators.rs — value-returning validators (email / UUID / IPv4 / IPv6).
|
|
2
|
+
//
|
|
3
|
+
// Two ABI shapes per validator (docs/FFI_BUN_GUIDE.md §3):
|
|
4
|
+
// - `castrum_validate_*` — `cstring` ARG: the engine transcodes the JS
|
|
5
|
+
// STRING to a call-scoped NUL-terminated UTF-8 buffer in-engine, so the
|
|
6
|
+
// JS side does zero encode work and the callee borrows via `CStr::from_ptr`
|
|
7
|
+
// (no copy). The fast path when the caller holds a JS string.
|
|
8
|
+
// - `castrum_validate_*_bytes` — `(ptr,len)` pair: zero transcode when the
|
|
9
|
+
// caller already holds BYTES (a cstring ARG would force a CString decode
|
|
10
|
+
// in JS and an engine re-encode — measured ~100 ns/call of pure waste;
|
|
11
|
+
// guide rule "cstring args are a LOSS for BYTE inputs").
|
|
12
|
+
|
|
13
|
+
/// Email / UUID / IPv4 / IPv6 validators → 1/0 (`cstring` ARG shape).
|
|
14
|
+
macro_rules! validator_c_abi {
|
|
15
|
+
($name:ident, $core:path) => {
|
|
16
|
+
#[doc = concat!("Validate input as a ", stringify!($name), " → 1/0.")]
|
|
17
|
+
///
|
|
18
|
+
/// # Safety
|
|
19
|
+
/// `data` must be a valid NUL-terminated C string.
|
|
20
|
+
#[no_mangle]
|
|
21
|
+
pub unsafe extern "C" fn $name(data: *const std::os::raw::c_char) -> u8 {
|
|
22
|
+
if data.is_null() {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
u8::from($core(std::ffi::CStr::from_ptr(data).to_bytes()))
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Email / UUID / IPv4 / IPv6 validators over raw bytes → 1/0 (`(ptr,len)` shape).
|
|
31
|
+
macro_rules! validator_bytes_c_abi {
|
|
32
|
+
($name:ident, $core:path) => {
|
|
33
|
+
#[doc = concat!("Validate raw bytes as a ", stringify!($name), " → 1/0.")]
|
|
34
|
+
///
|
|
35
|
+
/// Byte-input sibling of the `cstring`-ARG validator: no NUL
|
|
36
|
+
/// termination required (the length bounds the read), so JS callers
|
|
37
|
+
/// with existing byte buffers skip the decode + engine re-encode.
|
|
38
|
+
///
|
|
39
|
+
/// # Safety
|
|
40
|
+
/// `data` must be valid for reads of `len` bytes.
|
|
41
|
+
#[no_mangle]
|
|
42
|
+
pub unsafe extern "C" fn $name(data: *const u8, len: usize) -> u8 {
|
|
43
|
+
if data.is_null() && len != 0 {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
let bytes = if len == 0 {
|
|
47
|
+
&[]
|
|
48
|
+
} else {
|
|
49
|
+
std::slice::from_raw_parts(data, len)
|
|
50
|
+
};
|
|
51
|
+
u8::from($core(bytes))
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
validator_c_abi!(
|
|
57
|
+
castrum_validate_email,
|
|
58
|
+
crate::util::validation::validate_email_bytes
|
|
59
|
+
);
|
|
60
|
+
validator_c_abi!(
|
|
61
|
+
castrum_validate_uuid,
|
|
62
|
+
crate::util::validation::validate_uuid_bytes
|
|
63
|
+
);
|
|
64
|
+
validator_c_abi!(
|
|
65
|
+
castrum_validate_ipv4,
|
|
66
|
+
crate::util::validation::validate_ipv4_bytes
|
|
67
|
+
);
|
|
68
|
+
validator_c_abi!(
|
|
69
|
+
castrum_validate_ipv6,
|
|
70
|
+
crate::util::validation::validate_ipv6_bytes
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
validator_bytes_c_abi!(
|
|
74
|
+
castrum_validate_email_bytes,
|
|
75
|
+
crate::util::validation::validate_email_bytes
|
|
76
|
+
);
|
|
77
|
+
validator_bytes_c_abi!(
|
|
78
|
+
castrum_validate_uuid_bytes,
|
|
79
|
+
crate::util::validation::validate_uuid_bytes
|
|
80
|
+
);
|
|
81
|
+
validator_bytes_c_abi!(
|
|
82
|
+
castrum_validate_ipv4_bytes,
|
|
83
|
+
crate::util::validation::validate_ipv4_bytes
|
|
84
|
+
);
|
|
85
|
+
validator_bytes_c_abi!(
|
|
86
|
+
castrum_validate_ipv6_bytes,
|
|
87
|
+
crate::util::validation::validate_ipv6_bytes
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
/// Batch fixed-width hex validation: NEWLINE-separated lines in `data`, one
|
|
91
|
+
/// verdict byte (1 = exactly `width` hex chars, 0 = not) per line into `out`.
|
|
92
|
+
/// Needed-size convention: `0` = bad width / null pointers (real error);
|
|
93
|
+
/// `w > out_cap` = exact required size; else bytes written.
|
|
94
|
+
///
|
|
95
|
+
/// # Safety
|
|
96
|
+
/// `data` must be valid for reads of `len` bytes; `out` for writes up to
|
|
97
|
+
/// `out_cap`.
|
|
98
|
+
#[no_mangle]
|
|
99
|
+
pub unsafe extern "C" fn castrum_hex_validate_batch(
|
|
100
|
+
data: *const u8,
|
|
101
|
+
len: usize,
|
|
102
|
+
width: u32,
|
|
103
|
+
out: *mut u8,
|
|
104
|
+
out_cap: usize,
|
|
105
|
+
) -> usize {
|
|
106
|
+
if data.is_null() || out.is_null() {
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
let input = std::slice::from_raw_parts(data, len);
|
|
110
|
+
let needed = crate::util::validation::hex_batch_count(input);
|
|
111
|
+
if needed > out_cap {
|
|
112
|
+
return needed;
|
|
113
|
+
}
|
|
114
|
+
// `needed` verdict bytes fit — one bounded scratch Vec, one copy out.
|
|
115
|
+
let mut buf = Vec::with_capacity(needed);
|
|
116
|
+
match crate::util::validation::hex_batch_valid_into(input, width as usize, &mut buf) {
|
|
117
|
+
Ok(()) if buf.len() <= out_cap => {
|
|
118
|
+
std::slice::from_raw_parts_mut(out, buf.len()).copy_from_slice(&buf);
|
|
119
|
+
buf.len()
|
|
120
|
+
}
|
|
121
|
+
_ => 0,
|
|
122
|
+
}
|
|
123
|
+
}
|