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,373 @@
|
|
|
1
|
+
// rust/ffi/metrics.rs — metrics-registry C-ABI exports (`castrum_metrics_*`).
|
|
2
|
+
//
|
|
3
|
+
// Stateful handles owned by the CALLER (the route-stack ownership model):
|
|
4
|
+
// `castrum_metrics_create` returns a `Box<MetricsRegistry>` handle, every call
|
|
5
|
+
// takes it as a `usize`, and `castrum_metrics_destroy` frees it. Series ids
|
|
6
|
+
// are `u32` family indices (idempotent declaration), label VALUES cross packed
|
|
7
|
+
// `\x1f`-separated in one `(ptr,len)` buffer, and render follows the
|
|
8
|
+
// needed-size convention. A null handle (0) → safe failure (0 / sentinel),
|
|
9
|
+
// never a dereference.
|
|
10
|
+
|
|
11
|
+
use std::slice;
|
|
12
|
+
|
|
13
|
+
use super::util::panic_guard;
|
|
14
|
+
|
|
15
|
+
/// `u32::MAX` — the declare-failure sentinel (fits f64 exactly; real series
|
|
16
|
+
/// ids are dense from 0 and capped well below this).
|
|
17
|
+
pub(crate) const METRICS_DECLARE_ERR: u32 = u32::MAX;
|
|
18
|
+
|
|
19
|
+
/// Create an empty metrics registry → opaque handle (`0` = allocation panic).
|
|
20
|
+
#[no_mangle]
|
|
21
|
+
pub extern "C" fn castrum_metrics_create() -> usize {
|
|
22
|
+
let handle = Box::into_raw(Box::new(crate::metrics::MetricsRegistry::new()));
|
|
23
|
+
handle as usize
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Declare a counter family → series id (`METRICS_DECLARE_ERR` on error).
|
|
27
|
+
/// `label_keys` is a NUL-terminated `\x1f`-separated key list ("" = none).
|
|
28
|
+
///
|
|
29
|
+
/// # Safety
|
|
30
|
+
/// `handle` must be a live registry handle; `name`/`label_keys` valid C strings.
|
|
31
|
+
#[no_mangle]
|
|
32
|
+
pub unsafe extern "C" fn castrum_metrics_counter(
|
|
33
|
+
handle: usize,
|
|
34
|
+
name: *const std::os::raw::c_char,
|
|
35
|
+
label_keys: *const std::os::raw::c_char,
|
|
36
|
+
) -> u32 {
|
|
37
|
+
if handle == 0 || name.is_null() || label_keys.is_null() {
|
|
38
|
+
return METRICS_DECLARE_ERR;
|
|
39
|
+
}
|
|
40
|
+
let name = std::ffi::CStr::from_ptr(name).to_bytes();
|
|
41
|
+
let keys = split_keys(std::ffi::CStr::from_ptr(label_keys).to_bytes());
|
|
42
|
+
panic_guard(
|
|
43
|
+
|| {
|
|
44
|
+
crate::metrics::MetricsRegistry::declare(
|
|
45
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
46
|
+
crate::metrics::registry::MetricsKind::Counter,
|
|
47
|
+
std::str::from_utf8(name).unwrap_or_default(),
|
|
48
|
+
&keys,
|
|
49
|
+
&[],
|
|
50
|
+
)
|
|
51
|
+
},
|
|
52
|
+
Err(String::new()),
|
|
53
|
+
)
|
|
54
|
+
.unwrap_or(METRICS_DECLARE_ERR)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Declare a gauge family → series id. Same ABI as [`castrum_metrics_counter`].
|
|
58
|
+
///
|
|
59
|
+
/// # Safety
|
|
60
|
+
/// See [`castrum_metrics_counter`].
|
|
61
|
+
#[no_mangle]
|
|
62
|
+
pub unsafe extern "C" fn castrum_metrics_gauge(
|
|
63
|
+
handle: usize,
|
|
64
|
+
name: *const std::os::raw::c_char,
|
|
65
|
+
label_keys: *const std::os::raw::c_char,
|
|
66
|
+
) -> u32 {
|
|
67
|
+
if handle == 0 || name.is_null() || label_keys.is_null() {
|
|
68
|
+
return METRICS_DECLARE_ERR;
|
|
69
|
+
}
|
|
70
|
+
let name = std::ffi::CStr::from_ptr(name).to_bytes();
|
|
71
|
+
let keys = split_keys(std::ffi::CStr::from_ptr(label_keys).to_bytes());
|
|
72
|
+
panic_guard(
|
|
73
|
+
|| {
|
|
74
|
+
crate::metrics::MetricsRegistry::declare(
|
|
75
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
76
|
+
crate::metrics::registry::MetricsKind::Gauge,
|
|
77
|
+
std::str::from_utf8(name).unwrap_or_default(),
|
|
78
|
+
&keys,
|
|
79
|
+
&[],
|
|
80
|
+
)
|
|
81
|
+
},
|
|
82
|
+
Err(String::new()),
|
|
83
|
+
)
|
|
84
|
+
.unwrap_or(METRICS_DECLARE_ERR)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Declare a histogram family → series id. `buckets_csv` is a NUL-terminated
|
|
88
|
+
/// comma-separated finite positive list ("" = the default latency buckets).
|
|
89
|
+
///
|
|
90
|
+
/// # Safety
|
|
91
|
+
/// See [`castrum_metrics_counter`].
|
|
92
|
+
#[no_mangle]
|
|
93
|
+
pub unsafe extern "C" fn castrum_metrics_histogram(
|
|
94
|
+
handle: usize,
|
|
95
|
+
name: *const std::os::raw::c_char,
|
|
96
|
+
label_keys: *const std::os::raw::c_char,
|
|
97
|
+
buckets_csv: *const std::os::raw::c_char,
|
|
98
|
+
) -> u32 {
|
|
99
|
+
if handle == 0 || name.is_null() || label_keys.is_null() || buckets_csv.is_null() {
|
|
100
|
+
return METRICS_DECLARE_ERR;
|
|
101
|
+
}
|
|
102
|
+
let name = std::ffi::CStr::from_ptr(name).to_bytes();
|
|
103
|
+
let keys = split_keys(std::ffi::CStr::from_ptr(label_keys).to_bytes());
|
|
104
|
+
let csv = std::ffi::CStr::from_ptr(buckets_csv).to_bytes();
|
|
105
|
+
let buckets: Vec<f64> = std::str::from_utf8(csv)
|
|
106
|
+
.unwrap_or_default()
|
|
107
|
+
.split(',')
|
|
108
|
+
.filter_map(|s| s.trim().parse::<f64>().ok())
|
|
109
|
+
.collect();
|
|
110
|
+
panic_guard(
|
|
111
|
+
|| {
|
|
112
|
+
crate::metrics::MetricsRegistry::declare(
|
|
113
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
114
|
+
crate::metrics::registry::MetricsKind::Histogram,
|
|
115
|
+
std::str::from_utf8(name).unwrap_or_default(),
|
|
116
|
+
&keys,
|
|
117
|
+
&buckets,
|
|
118
|
+
)
|
|
119
|
+
},
|
|
120
|
+
Err(String::new()),
|
|
121
|
+
)
|
|
122
|
+
.unwrap_or(METRICS_DECLARE_ERR)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Record one event: counter/gauge `+= amount`, histogram observe `amount`.
|
|
126
|
+
/// `values` is the packed `\x1f`-separated label values. Returns 1 = ok.
|
|
127
|
+
///
|
|
128
|
+
/// # Safety
|
|
129
|
+
/// `handle` must be live; `values` valid for reads of `values_len` bytes.
|
|
130
|
+
#[no_mangle]
|
|
131
|
+
pub unsafe extern "C" fn castrum_metrics_record(
|
|
132
|
+
handle: usize,
|
|
133
|
+
series: u32,
|
|
134
|
+
values: *const u8,
|
|
135
|
+
values_len: usize,
|
|
136
|
+
amount: f64,
|
|
137
|
+
) -> u8 {
|
|
138
|
+
if handle == 0 || (values.is_null() && values_len != 0) {
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
let vals = if values_len == 0 {
|
|
142
|
+
&[]
|
|
143
|
+
} else {
|
|
144
|
+
slice::from_raw_parts(values, values_len)
|
|
145
|
+
};
|
|
146
|
+
panic_guard(
|
|
147
|
+
|| {
|
|
148
|
+
crate::metrics::MetricsRegistry::add(
|
|
149
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
150
|
+
series,
|
|
151
|
+
vals,
|
|
152
|
+
amount,
|
|
153
|
+
)
|
|
154
|
+
},
|
|
155
|
+
Err(String::new()),
|
|
156
|
+
)
|
|
157
|
+
.is_ok() as u8
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Gauge assignment (`= value`). Returns 1 = ok.
|
|
161
|
+
///
|
|
162
|
+
/// # Safety
|
|
163
|
+
/// See [`castrum_metrics_record`].
|
|
164
|
+
#[no_mangle]
|
|
165
|
+
pub unsafe extern "C" fn castrum_metrics_gauge_set(
|
|
166
|
+
handle: usize,
|
|
167
|
+
series: u32,
|
|
168
|
+
values: *const u8,
|
|
169
|
+
values_len: usize,
|
|
170
|
+
value: f64,
|
|
171
|
+
) -> u8 {
|
|
172
|
+
if handle == 0 || (values.is_null() && values_len != 0) {
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
let vals = if values_len == 0 {
|
|
176
|
+
&[]
|
|
177
|
+
} else {
|
|
178
|
+
slice::from_raw_parts(values, values_len)
|
|
179
|
+
};
|
|
180
|
+
panic_guard(
|
|
181
|
+
|| {
|
|
182
|
+
crate::metrics::MetricsRegistry::set(
|
|
183
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
184
|
+
series,
|
|
185
|
+
vals,
|
|
186
|
+
value,
|
|
187
|
+
)
|
|
188
|
+
},
|
|
189
|
+
Err(String::new()),
|
|
190
|
+
)
|
|
191
|
+
.is_ok() as u8
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Record one event with the label VALUES crossing as a SINGLE `cstring` ARG
|
|
195
|
+
/// (`\x1f`-joined) — the engine transcodes the JS string in-engine, so the JS
|
|
196
|
+
/// hot path does ZERO `TextEncoder` work (`values.join('\x1f')` is all it
|
|
197
|
+
/// pays). Values must not contain NUL (a NUL would truncate the list — label
|
|
198
|
+
/// values are validated against NUL anyway). Returns 1 = ok.
|
|
199
|
+
///
|
|
200
|
+
/// # Safety
|
|
201
|
+
/// `handle` must be live; `values` a valid NUL-terminated C string.
|
|
202
|
+
#[no_mangle]
|
|
203
|
+
pub unsafe extern "C" fn castrum_metrics_record_str(
|
|
204
|
+
handle: usize,
|
|
205
|
+
series: u32,
|
|
206
|
+
values: *const std::os::raw::c_char,
|
|
207
|
+
amount: f64,
|
|
208
|
+
) -> u8 {
|
|
209
|
+
if handle == 0 || values.is_null() {
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
let vals = std::ffi::CStr::from_ptr(values).to_bytes();
|
|
213
|
+
panic_guard(
|
|
214
|
+
|| {
|
|
215
|
+
crate::metrics::MetricsRegistry::add(
|
|
216
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
217
|
+
series,
|
|
218
|
+
vals,
|
|
219
|
+
amount,
|
|
220
|
+
)
|
|
221
|
+
},
|
|
222
|
+
Err(String::new()),
|
|
223
|
+
)
|
|
224
|
+
.is_ok() as u8
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// Gauge assignment (`= value`) with `\x1f`-joined `cstring` values — the
|
|
228
|
+
/// zero-encode sibling of [`castrum_metrics_gauge_set`]. Returns 1 = ok.
|
|
229
|
+
///
|
|
230
|
+
/// # Safety
|
|
231
|
+
/// See [`castrum_metrics_record_str`].
|
|
232
|
+
#[no_mangle]
|
|
233
|
+
pub unsafe extern "C" fn castrum_metrics_gauge_set_str(
|
|
234
|
+
handle: usize,
|
|
235
|
+
series: u32,
|
|
236
|
+
values: *const std::os::raw::c_char,
|
|
237
|
+
value: f64,
|
|
238
|
+
) -> u8 {
|
|
239
|
+
if handle == 0 || values.is_null() {
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
let vals = std::ffi::CStr::from_ptr(values).to_bytes();
|
|
243
|
+
panic_guard(
|
|
244
|
+
|| {
|
|
245
|
+
crate::metrics::MetricsRegistry::set(
|
|
246
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
247
|
+
series,
|
|
248
|
+
vals,
|
|
249
|
+
value,
|
|
250
|
+
)
|
|
251
|
+
},
|
|
252
|
+
Err(String::new()),
|
|
253
|
+
)
|
|
254
|
+
.is_ok() as u8
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// Render the Prometheus text format into `out` (needed-size convention:
|
|
258
|
+
/// `0` = null handle / render panic; `w > out_cap` = exact required size).
|
|
259
|
+
///
|
|
260
|
+
/// # Safety
|
|
261
|
+
/// `handle` must be live; `out` for writes up to `out_cap`.
|
|
262
|
+
#[no_mangle]
|
|
263
|
+
pub unsafe extern "C" fn castrum_metrics_render(
|
|
264
|
+
handle: usize,
|
|
265
|
+
out: *mut u8,
|
|
266
|
+
out_cap: usize,
|
|
267
|
+
) -> usize {
|
|
268
|
+
if handle == 0 || out.is_null() {
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
let rendered = panic_guard(
|
|
272
|
+
|| {
|
|
273
|
+
crate::metrics::MetricsRegistry::render(unsafe {
|
|
274
|
+
&*(handle as *const crate::metrics::MetricsRegistry)
|
|
275
|
+
})
|
|
276
|
+
},
|
|
277
|
+
Vec::new(),
|
|
278
|
+
);
|
|
279
|
+
if rendered.len() > out_cap {
|
|
280
|
+
return rendered.len();
|
|
281
|
+
}
|
|
282
|
+
slice::from_raw_parts_mut(out, rendered.len()).copy_from_slice(&rendered);
|
|
283
|
+
rendered.len()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// Destroy a registry handle (idempotent-safe against null only; double-free
|
|
287
|
+
/// is caller error, same contract as `castrum_route_destroy`).
|
|
288
|
+
///
|
|
289
|
+
/// # Safety
|
|
290
|
+
/// `handle` must be either 0 or a live handle not yet destroyed.
|
|
291
|
+
#[no_mangle]
|
|
292
|
+
pub unsafe extern "C" fn castrum_metrics_destroy(handle: usize) {
|
|
293
|
+
if handle != 0 {
|
|
294
|
+
drop(Box::from_raw(
|
|
295
|
+
handle as *mut crate::metrics::MetricsRegistry,
|
|
296
|
+
));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Split a `\x1f`-separated key list into owned strings (empty input → []).
|
|
301
|
+
fn split_keys(bytes: &[u8]) -> Vec<&str> {
|
|
302
|
+
if bytes.is_empty() {
|
|
303
|
+
return Vec::new();
|
|
304
|
+
}
|
|
305
|
+
std::str::from_utf8(bytes)
|
|
306
|
+
.unwrap_or_default()
|
|
307
|
+
.split('\x1f')
|
|
308
|
+
.collect()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// Dump the packed series snapshot (v1 wire format — see
|
|
312
|
+
/// `crate::metrics::registry::MetricsRegistry::snapshot_into`) into `out`.
|
|
313
|
+
/// Needed-size convention: `0` = null handle; `w > out_cap` = exact required
|
|
314
|
+
/// size; else bytes written.
|
|
315
|
+
///
|
|
316
|
+
/// # Safety
|
|
317
|
+
/// `handle` must be live; `out` for writes up to `out_cap`.
|
|
318
|
+
#[no_mangle]
|
|
319
|
+
pub unsafe extern "C" fn castrum_metrics_snapshot(
|
|
320
|
+
handle: usize,
|
|
321
|
+
out: *mut u8,
|
|
322
|
+
out_cap: usize,
|
|
323
|
+
) -> usize {
|
|
324
|
+
if handle == 0 || out.is_null() {
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
let dumped = panic_guard(
|
|
328
|
+
|| {
|
|
329
|
+
crate::metrics::MetricsRegistry::snapshot(unsafe {
|
|
330
|
+
&*(handle as *const crate::metrics::MetricsRegistry)
|
|
331
|
+
})
|
|
332
|
+
},
|
|
333
|
+
Vec::new(),
|
|
334
|
+
);
|
|
335
|
+
if dumped.len() > out_cap {
|
|
336
|
+
return dumped.len();
|
|
337
|
+
}
|
|
338
|
+
slice::from_raw_parts_mut(out, dumped.len()).copy_from_slice(&dumped);
|
|
339
|
+
dumped.len()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/// Record a BATCH of events in one crossing. Packed layout:
|
|
343
|
+
/// `[u32 n]{[u32 series][u32 valsLen][vals][f64 amount]}`. Returns 1 = all
|
|
344
|
+
/// applied; 0 = null handle / truncated / any entry failed (entries before a
|
|
345
|
+
/// failure stay applied).
|
|
346
|
+
///
|
|
347
|
+
/// # Safety
|
|
348
|
+
/// `handle` must be live; `packed` valid for reads of `packed_len` bytes.
|
|
349
|
+
#[no_mangle]
|
|
350
|
+
pub unsafe extern "C" fn castrum_metrics_record_batch(
|
|
351
|
+
handle: usize,
|
|
352
|
+
packed: *const u8,
|
|
353
|
+
packed_len: usize,
|
|
354
|
+
) -> u8 {
|
|
355
|
+
if handle == 0 || (packed.is_null() && packed_len != 0) {
|
|
356
|
+
return 0;
|
|
357
|
+
}
|
|
358
|
+
let buf = if packed_len == 0 {
|
|
359
|
+
&[]
|
|
360
|
+
} else {
|
|
361
|
+
slice::from_raw_parts(packed, packed_len)
|
|
362
|
+
};
|
|
363
|
+
panic_guard(
|
|
364
|
+
|| {
|
|
365
|
+
crate::metrics::MetricsRegistry::record_batch(
|
|
366
|
+
unsafe { &*(handle as *const crate::metrics::MetricsRegistry) },
|
|
367
|
+
buf,
|
|
368
|
+
)
|
|
369
|
+
},
|
|
370
|
+
Err(String::new()),
|
|
371
|
+
)
|
|
372
|
+
.is_ok() as u8
|
|
373
|
+
}
|
package/rust/ffi/mod.rs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//! C-ABI (`extern "C"`) exports for `bun:ffi` (Bun's JIT-compiled FFI,
|
|
2
|
+
//! ~10-20ns crossing vs ~100-300ns for N-API). The same cdylib serves BOTH
|
|
3
|
+
//! runtimes: Node loads it through napi-rs (napi_register_module_v1), Bun can
|
|
4
|
+
//! additionally `dlopen` it via `bun:ffi` and call these functions directly.
|
|
5
|
+
//!
|
|
6
|
+
//! The stateless `&[u8] -> scalar` / `&[u8] -> write-into-buffer` hot functions
|
|
7
|
+
//! (where the N-API crossing dominates the sub-µs cost) are the bulk of the
|
|
8
|
+
//! surface. The stateful ingress pipeline is ALSO exposed via
|
|
9
|
+
//! `castrum_ingress_handle_packed`, which takes the opaque inner handle from
|
|
10
|
+
//! `Ingress.ingressInnerPtr()` (valid only while the instance is alive — see
|
|
11
|
+
//! the function's safety note). Buffer-taking fns use raw `(ptr, len)` pairs;
|
|
12
|
+
//! output-buffer fns use `(out, out_cap)` and return bytes written (0 = error /
|
|
13
|
+
//! too small).
|
|
14
|
+
//!
|
|
15
|
+
//! # Safety
|
|
16
|
+
//! Every function is `unsafe` in the Rust sense (raw pointers) but keeps the
|
|
17
|
+
//! read/write within the caller-declared lengths. Callers (bun:ffi bindings in
|
|
18
|
+
//! `src/native/ffi.ts`) must pass pointers into live buffers of at least the
|
|
19
|
+
//! declared length, and for `castrum_ingress_handle_packed` a live handle from a
|
|
20
|
+
//! live `Ingress` instance.
|
|
21
|
+
//!
|
|
22
|
+
//! # Layout
|
|
23
|
+
//! Split by domain so the ~80 `castrum_*` symbols stay navigable. The symbol
|
|
24
|
+
//! registry contract (the dlopen map in `src/native/ffi.ts` + the source-level
|
|
25
|
+
//! parity guard in `test/unit/features/ffi-symbol-parity.test.ts`) is agnostic
|
|
26
|
+
//! to module layout — symbol NAMES never move, only the files they live in:
|
|
27
|
+
//! - `util.rs` — shared helpers (`panic_guard`, `hmac_key_cached`, `aead_alg`,
|
|
28
|
+
//! `cstring_return`/`CSTR_BUF`, `write_rate_check`)
|
|
29
|
+
//! - `hashing.rs` — crc32 / fnv1a / xxh3, json/utf8 validity, hex/url codecs
|
|
30
|
+
//! - `validators.rs` — email / UUID / IPv4 / IPv6 (`validator_c_abi!`)
|
|
31
|
+
//! - `crypto.rs` — hmac, cookie sign/verify, csrf, password hash/verify, pbkdf2,
|
|
32
|
+
//! aead, base64, random token
|
|
33
|
+
//! - `jwt.rs` — jwt signer/verify + ed25519 / EdDSA JWT
|
|
34
|
+
//! - `http.rs` — etag, conditional, http-date, media-type, accept, mime, url
|
|
35
|
+
//! resolve/encode, http/query/cookie/form/multipart parse
|
|
36
|
+
//! - `payload.rs` — ws accept key + frames, gzip/brotli, sse
|
|
37
|
+
//! - `json.rs` — json patch, packed json token stream, schema validator
|
|
38
|
+
//! - `rate_limit.rs` — rate limiter checks
|
|
39
|
+
//! - `metrics.rs` — metrics registry (`castrum_metrics_*`, caller-owned handles)
|
|
40
|
+
//! - `ingress.rs` — ingress pipeline + layout constants
|
|
41
|
+
//! - `route.rs` — per-route native stack (`castrum_route_*`)
|
|
42
|
+
//! - `tests.rs` — C-ABI unit tests
|
|
43
|
+
|
|
44
|
+
mod crypto;
|
|
45
|
+
mod hashing;
|
|
46
|
+
mod http;
|
|
47
|
+
mod ingress;
|
|
48
|
+
mod json;
|
|
49
|
+
mod jwt;
|
|
50
|
+
mod metrics;
|
|
51
|
+
mod payload;
|
|
52
|
+
mod probe;
|
|
53
|
+
mod rate_limit;
|
|
54
|
+
mod route;
|
|
55
|
+
mod util;
|
|
56
|
+
mod validators;
|
|
57
|
+
|
|
58
|
+
#[cfg(test)]
|
|
59
|
+
mod tests;
|
|
60
|
+
|
|
61
|
+
// Re-export every symbol at the `ffi` module root so the shared `tests.rs`
|
|
62
|
+
// (`use super::*`) keeps working unchanged. The `#[no_mangle]` link symbols are
|
|
63
|
+
// independent of Rust visibility, so nothing in non-test code needs them.
|
|
64
|
+
#[cfg(test)]
|
|
65
|
+
pub(crate) use self::{
|
|
66
|
+
crypto::*, hashing::*, http::*, ingress::*, json::*, jwt::*, metrics::*, payload::*,
|
|
67
|
+
rate_limit::*, route::*, util::*, validators::*,
|
|
68
|
+
};
|