castrum 0.9.3 → 0.9.6
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 +2242 -0
- package/Cargo.toml +155 -0
- package/README.md +14 -6
- package/build.rs +5 -0
- 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 +1508 -1291
- package/dist/index.js +3391 -2069
- package/docs/API.md +2 -1
- package/docs/ARCHITECTURE.md +3 -2
- package/docs/CASE_STUDY.md +5 -3
- package/docs/ENVIRONMENT.md +25 -2
- package/docs/FFI_BUN_GUIDE.md +242 -14
- package/docs/HOW_TO_ADD_AN_OP.md +10 -0
- package/docs/REPO_MAP.md +17 -6
- package/docs/RND-CONCURRENCY.md +772 -0
- package/docs/bun-builtins-decision-matrix.md +4 -1
- package/index.ts +24 -25
- package/package.json +173 -156
- 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 +105 -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 +69 -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/task.rs +172 -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 +136 -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 +211 -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 +770 -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 +567 -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 +883 -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 +73 -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/task/completion.rs +119 -0
- package/rust/task/mod.rs +65 -0
- package/rust/task/ops.rs +503 -0
- package/rust/task/runtime.rs +220 -0
- package/rust/task/tests.rs +231 -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 +594 -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/health.ts +1 -2
- 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 +130 -12
- package/src/native/ffi/build/compress.ts +22 -10
- package/src/native/ffi/build/instances.ts +46 -4
- package/src/native/ffi/build/metrics.ts +247 -0
- package/src/native/ffi/build/parse.ts +59 -23
- package/src/native/ffi/build/task.ts +79 -0
- package/src/native/ffi/build/util.ts +32 -1
- package/src/native/ffi/build.ts +4 -0
- package/src/native/ffi/selftest.ts +12 -3
- package/src/native/ffi/types.ts +151 -9
- package/src/native/ffi.ts +160 -15
- package/src/native/index.ts +1 -1
- package/src/native/loader.ts +55 -5
- 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 +71 -27
- 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/rust-ffi/text.ts +25 -3
- package/src/selection.ts +1 -1
- package/src/shared/bytes.ts +24 -4
- package/src/shared/codec.ts +18 -3
- package/src/task/index.ts +24 -0
- package/src/task/op.ts +171 -0
- package/src/task/runtime.ts +568 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// rust/ffi/payload.rs — payload C-ABI exports.
|
|
2
|
+
//
|
|
3
|
+
// WebSocket accept-key + frame encode/decode, gzip/brotli compress-decompress
|
|
4
|
+
// (via the shared `compress_to_out!` macro — see the needed-size convention),
|
|
5
|
+
// and SSE event encoding.
|
|
6
|
+
|
|
7
|
+
use std::slice;
|
|
8
|
+
|
|
9
|
+
use super::util::{cstring_return, panic_guard};
|
|
10
|
+
|
|
11
|
+
/// RFC 6455 Sec-WebSocket-Accept (28 bytes) returned as a null-terminated C
|
|
12
|
+
/// string into the per-thread reused buffer (`cstring` return).
|
|
13
|
+
///
|
|
14
|
+
/// `key` is a `bun:ffi` `cstring` ARG — the engine transcodes the JS base64 key
|
|
15
|
+
/// in-engine (JS does zero encode; the callee borrows via `CStr::from_ptr`).
|
|
16
|
+
/// The pooled `castrum_ws_accept_key_into` keeps the `(ptr,len)` byte form.
|
|
17
|
+
///
|
|
18
|
+
/// # Safety
|
|
19
|
+
/// `key` must be a valid NUL-terminated C string.
|
|
20
|
+
#[no_mangle]
|
|
21
|
+
pub unsafe extern "C" fn castrum_ws_accept_key(
|
|
22
|
+
key: *const std::os::raw::c_char,
|
|
23
|
+
) -> *const std::os::raw::c_char {
|
|
24
|
+
if key.is_null() {
|
|
25
|
+
return std::ptr::null();
|
|
26
|
+
}
|
|
27
|
+
cstring_return(28, |out| {
|
|
28
|
+
crate::payload::websocket::ws_accept_key_into(std::ffi::CStr::from_ptr(key).to_bytes(), out)
|
|
29
|
+
.ok()
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// RFC 6455 Sec-WebSocket-Accept written directly into a caller buffer — the
|
|
34
|
+
/// pooled sibling of `castrum_ws_accept_key` (no cstring round-trip). Writes 28
|
|
35
|
+
/// bytes; returns bytes written, the exact required size when `out_cap` is too
|
|
36
|
+
/// small, or 0 on a malformed key.
|
|
37
|
+
///
|
|
38
|
+
/// # Safety
|
|
39
|
+
/// `key` must be valid for reads of `klen` bytes; `out` for writes up to `out_cap`.
|
|
40
|
+
#[no_mangle]
|
|
41
|
+
pub unsafe extern "C" fn castrum_ws_accept_key_into(
|
|
42
|
+
key: *const u8,
|
|
43
|
+
klen: usize,
|
|
44
|
+
out: *mut u8,
|
|
45
|
+
out_cap: usize,
|
|
46
|
+
) -> usize {
|
|
47
|
+
if key.is_null() || out.is_null() {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
if out_cap < 28 {
|
|
51
|
+
return 28;
|
|
52
|
+
}
|
|
53
|
+
match crate::payload::websocket::ws_accept_key_into(
|
|
54
|
+
slice::from_raw_parts(key, klen),
|
|
55
|
+
slice::from_raw_parts_mut(out, 28),
|
|
56
|
+
) {
|
|
57
|
+
Ok(28) => 28,
|
|
58
|
+
_ => 0,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// WebSocket frame encode into `out` (opcode 1/2/8/9/10, `mask`/`fin` flags).
|
|
63
|
+
///
|
|
64
|
+
/// # Safety
|
|
65
|
+
/// `payload` must be valid for reads of `plen` bytes; `out` for `out_cap`.
|
|
66
|
+
#[no_mangle]
|
|
67
|
+
pub unsafe extern "C" fn castrum_ws_frame_encode(
|
|
68
|
+
opcode: u8,
|
|
69
|
+
payload: *const u8,
|
|
70
|
+
plen: usize,
|
|
71
|
+
mask: u8,
|
|
72
|
+
fin: u8,
|
|
73
|
+
out: *mut u8,
|
|
74
|
+
out_cap: usize,
|
|
75
|
+
) -> usize {
|
|
76
|
+
if payload.is_null() || out.is_null() {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
crate::payload::ws_frames::encode_frame_into(
|
|
80
|
+
opcode,
|
|
81
|
+
slice::from_raw_parts(payload, plen),
|
|
82
|
+
mask != 0,
|
|
83
|
+
fin != 0,
|
|
84
|
+
slice::from_raw_parts_mut(out, out_cap),
|
|
85
|
+
)
|
|
86
|
+
.unwrap_or(0)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Decode a WebSocket frame into a packed `[u8 flags][u8 opcode][u32 payload_len]
|
|
90
|
+
/// [payload]` layout (flags bit0 = FIN). Returns bytes written, 0 on malformed
|
|
91
|
+
/// input. JS decodes the small header into the `WsFrame` shape.
|
|
92
|
+
///
|
|
93
|
+
/// # Safety
|
|
94
|
+
/// `data` must be valid for reads of `len` bytes; `out` for writes up to `out_cap`.
|
|
95
|
+
#[no_mangle]
|
|
96
|
+
pub unsafe extern "C" fn castrum_ws_frame_decode_packed(
|
|
97
|
+
data: *const u8,
|
|
98
|
+
len: usize,
|
|
99
|
+
out: *mut u8,
|
|
100
|
+
out_cap: usize,
|
|
101
|
+
) -> usize {
|
|
102
|
+
if data.is_null() || out.is_null() {
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
let Some(frame) = crate::payload::ws_frames::decode_frame(slice::from_raw_parts(data, len))
|
|
106
|
+
else {
|
|
107
|
+
return 0;
|
|
108
|
+
};
|
|
109
|
+
let Some(need) = 6usize.checked_add(frame.payload.len()) else {
|
|
110
|
+
return 0;
|
|
111
|
+
};
|
|
112
|
+
if need > out_cap {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
let o = slice::from_raw_parts_mut(out, need);
|
|
116
|
+
o[0] = u8::from(frame.fin);
|
|
117
|
+
o[1] = frame.opcode;
|
|
118
|
+
o[2..6].copy_from_slice(&(frame.payload.len() as u32).to_le_bytes());
|
|
119
|
+
o[6..].copy_from_slice(&frame.payload);
|
|
120
|
+
need
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Compression (gzip / brotli) ──────────────────────────────────
|
|
124
|
+
|
|
125
|
+
macro_rules! compress_to_out {
|
|
126
|
+
($name:ident, $core:path, $extra:ty) => {
|
|
127
|
+
#[doc = concat!("Run `", stringify!($core), "` and write the result into `out`.")]
|
|
128
|
+
///
|
|
129
|
+
/// # Convention
|
|
130
|
+
/// Returns the exact total bytes the result requires (the written count
|
|
131
|
+
/// on success, or the needed size when `out_cap` is too small). The JS
|
|
132
|
+
/// wrapper compares against `out_cap` to distinguish success from
|
|
133
|
+
/// too-small, so a miss allocates EXACTLY once and retries — it never
|
|
134
|
+
/// re-runs the whole (de)compression in a grow loop. `0` is reserved
|
|
135
|
+
/// for a REAL error (invalid stream / decompression-cap exceeded).
|
|
136
|
+
///
|
|
137
|
+
/// # Safety
|
|
138
|
+
/// `data` must be valid for reads of `len` bytes; `out` for writes up to `out_cap`.
|
|
139
|
+
#[no_mangle]
|
|
140
|
+
pub unsafe extern "C" fn $name(
|
|
141
|
+
data: *const u8,
|
|
142
|
+
len: usize,
|
|
143
|
+
extra: $extra,
|
|
144
|
+
out: *mut u8,
|
|
145
|
+
out_cap: usize,
|
|
146
|
+
) -> usize {
|
|
147
|
+
if data.is_null() || out.is_null() {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
let input = slice::from_raw_parts(data, len);
|
|
151
|
+
let output = slice::from_raw_parts_mut(out, out_cap);
|
|
152
|
+
panic_guard(
|
|
153
|
+
|| match $core(input, extra, output) {
|
|
154
|
+
Ok(w) => w,
|
|
155
|
+
// Needed-size convention: report the EXACT required size so the
|
|
156
|
+
// JS wrapper allocates once and retries (never a re-run loop).
|
|
157
|
+
Err(crate::payload::compress::StreamError::TooSmall { needed }) => needed,
|
|
158
|
+
// Real error (invalid stream / decompression cap exceeded) → 0.
|
|
159
|
+
Err(_) => 0,
|
|
160
|
+
},
|
|
161
|
+
0,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
compress_to_out!(
|
|
168
|
+
castrum_gzip_compress,
|
|
169
|
+
crate::payload::compress::gzip_compress_into,
|
|
170
|
+
u32
|
|
171
|
+
);
|
|
172
|
+
compress_to_out!(
|
|
173
|
+
castrum_gzip_decompress,
|
|
174
|
+
crate::payload::compress::gzip_decompress_into,
|
|
175
|
+
usize
|
|
176
|
+
);
|
|
177
|
+
compress_to_out!(
|
|
178
|
+
castrum_brotli_compress,
|
|
179
|
+
crate::payload::compress::brotli_compress_into,
|
|
180
|
+
u32
|
|
181
|
+
);
|
|
182
|
+
compress_to_out!(
|
|
183
|
+
castrum_brotli_decompress,
|
|
184
|
+
crate::payload::compress::brotli_decompress_into,
|
|
185
|
+
usize
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
/// Read the original (uncompressed) size from a gzip stream's ISIZE trailer
|
|
189
|
+
/// (last 4 bytes, little-endian, original size mod 2^32). Returns the EXACT
|
|
190
|
+
/// output size for a single-member standard gzip stream, or 0 when the input
|
|
191
|
+
/// isn't a usable standard gzip stream (too short / not gzip magic). Lets the
|
|
192
|
+
/// JS wrapper pre-size the decompress buffer exactly so the happy path is a
|
|
193
|
+
/// single pass (no grow-retry re-run).
|
|
194
|
+
///
|
|
195
|
+
/// # Safety
|
|
196
|
+
/// `data` must be valid for reads of `len` bytes.
|
|
197
|
+
#[no_mangle]
|
|
198
|
+
pub unsafe extern "C" fn castrum_gzip_isize(data: *const u8, len: usize) -> u32 {
|
|
199
|
+
if data.is_null() || len < 18 {
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
let s = slice::from_raw_parts(data, len);
|
|
203
|
+
if s[0] != 0x1f || s[1] != 0x8b {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
u32::from_le_bytes([s[len - 4], s[len - 3], s[len - 2], s[len - 1]])
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// Encode one SSE event into `out`. Optional fields use `flags` bits
|
|
210
|
+
/// (1 = event present, 2 = id present, 4 = retry present) so a present-but-empty
|
|
211
|
+
/// string is distinct from an absent one, matching the napi `Option<String>`.
|
|
212
|
+
/// Returns bytes written, or 0 on a too-small buffer / invalid UTF-8 in
|
|
213
|
+
/// event/id (JS sizes `data_len + 64` to guarantee success). FFI sibling of the
|
|
214
|
+
/// napi `sse_encode_event` — the wrapper decodes nothing (output is raw SSE
|
|
215
|
+
/// bytes), so this just removes the napi crossing.
|
|
216
|
+
///
|
|
217
|
+
/// # Safety
|
|
218
|
+
/// `event`/`id` must be valid for reads of `event_len`/`id_len` bytes when their
|
|
219
|
+
/// flag is set; `data` valid for `data_len` reads; `out` for `out_cap` writes.
|
|
220
|
+
#[no_mangle]
|
|
221
|
+
pub unsafe extern "C" fn castrum_sse_encode_into(
|
|
222
|
+
event: *const u8,
|
|
223
|
+
event_len: usize,
|
|
224
|
+
data: *const u8,
|
|
225
|
+
data_len: usize,
|
|
226
|
+
id: *const u8,
|
|
227
|
+
id_len: usize,
|
|
228
|
+
flags: u8,
|
|
229
|
+
retry: u32,
|
|
230
|
+
out: *mut u8,
|
|
231
|
+
out_cap: usize,
|
|
232
|
+
) -> usize {
|
|
233
|
+
if data.is_null() || out.is_null() {
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
// A PRESENT but empty event/id is valid (emits the line) — only a null
|
|
237
|
+
// pointer with its flag set is malformed (would be UB to slice).
|
|
238
|
+
if (flags & 1 != 0 && event.is_null()) || (flags & 2 != 0 && id.is_null()) {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
panic_guard(
|
|
242
|
+
|| -> Option<usize> {
|
|
243
|
+
let data_bytes = slice::from_raw_parts(data, data_len);
|
|
244
|
+
let output = slice::from_raw_parts_mut(out, out_cap);
|
|
245
|
+
let event_opt = if flags & 1 != 0 {
|
|
246
|
+
Some(std::str::from_utf8(slice::from_raw_parts(event, event_len)).ok()?)
|
|
247
|
+
} else {
|
|
248
|
+
None
|
|
249
|
+
};
|
|
250
|
+
let id_opt = if flags & 2 != 0 {
|
|
251
|
+
Some(std::str::from_utf8(slice::from_raw_parts(id, id_len)).ok()?)
|
|
252
|
+
} else {
|
|
253
|
+
None
|
|
254
|
+
};
|
|
255
|
+
let retry_opt = if flags & 4 != 0 {
|
|
256
|
+
Some(u64::from(retry))
|
|
257
|
+
} else {
|
|
258
|
+
None
|
|
259
|
+
};
|
|
260
|
+
match crate::payload::sse::encode_event_into_slice(
|
|
261
|
+
event_opt, data_bytes, id_opt, retry_opt, output,
|
|
262
|
+
) {
|
|
263
|
+
Ok(w) => Some(w),
|
|
264
|
+
// Too-small buffer: return the exact required size so the JS
|
|
265
|
+
// wrapper allocates ONCE and retries (needed-size convention);
|
|
266
|
+
// 0 stays a real error (invalid UTF-8 in event/id above).
|
|
267
|
+
Err(_) => Some(crate::payload::sse::encode_event_size(
|
|
268
|
+
event_opt, data_bytes, id_opt, retry_opt,
|
|
269
|
+
)),
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
None,
|
|
273
|
+
)
|
|
274
|
+
.unwrap_or(0)
|
|
275
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// rust/ffi/probe.rs — diagnostic C-ABI probes (bench-only, NOT in the ffi.ts surface).
|
|
2
|
+
//
|
|
3
|
+
// Used ONLY by bench/ffi-margin.ts to isolate the fixed per-call FFI cost into
|
|
4
|
+
// its components: the bare trampoline (noop), scalar-arg/return conversion
|
|
5
|
+
// (echo_usize, which can be bound with either `usize` or `u64_fast` to measure
|
|
6
|
+
// BigInt boxing in isolation), TypedArray-view→pointer resolution
|
|
7
|
+
// (echo_view), and `cstring`-ARG transcoding (echo_cstr — the engine encodes
|
|
8
|
+
// the JS string to a call-scoped NUL-terminated buffer; the callee borrows it
|
|
9
|
+
// via `CStr::from_ptr`, never dereferencing beyond NUL). They are trivial,
|
|
10
|
+
// non-fallible, allocate nothing, and are NOT part of the shipped
|
|
11
|
+
// `src/native/ffi.ts` dlopen map, so the bind-time self-test does not cover
|
|
12
|
+
// them. The `ffi_probe_*` prefix (NOT `castrum_*`) keeps them out of the
|
|
13
|
+
// shipped-symbol namespace so the source-level symbol-parity guard
|
|
14
|
+
// (test/unit/features/ffi-symbol-parity.test.ts) stays meaningful.
|
|
15
|
+
|
|
16
|
+
use std::slice;
|
|
17
|
+
|
|
18
|
+
/// Bare C-ABI trampoline floor: does nothing, returns nothing.
|
|
19
|
+
#[no_mangle]
|
|
20
|
+
pub extern "C" fn ffi_probe_noop() {}
|
|
21
|
+
|
|
22
|
+
/// Scalar pass-through: returns `v` unchanged. Measures scalar-arg + return
|
|
23
|
+
/// conversion only (bind with `usize` vs `u64_fast` to isolate BigInt boxing).
|
|
24
|
+
#[no_mangle]
|
|
25
|
+
pub extern "C" fn ffi_probe_echo_usize(v: usize) -> usize {
|
|
26
|
+
v
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// View pass-through: returns the byte length of the `(ptr, len)` pair.
|
|
30
|
+
/// Measures TypedArray-view→pointer resolution + (ptr,len) arg conversion.
|
|
31
|
+
///
|
|
32
|
+
/// # Safety
|
|
33
|
+
/// `data` must be valid for reads of `len` bytes (the pointer is only used to
|
|
34
|
+
/// form a slice whose length we return — never dereferenced).
|
|
35
|
+
#[no_mangle]
|
|
36
|
+
pub unsafe extern "C" fn ffi_probe_echo_view(data: *const u8, len: usize) -> usize {
|
|
37
|
+
if data.is_null() && len != 0 {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
let _ = slice::from_raw_parts(data, len);
|
|
41
|
+
len
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// `cstring`-ARG pass-through: returns the byte length of the NUL-terminated
|
|
45
|
+
/// C string the engine produced by transcoding a JS string arg. Measures the
|
|
46
|
+
/// engine-side JS-string→UTF-8 transcode + call-scoped buffer + callee
|
|
47
|
+
/// `CStr::from_ptr` borrow — the cost a `'cstring'` input arg adds compared
|
|
48
|
+
/// with a JS-side `encoder.encode` + `(ptr,len)` pair. The pointer is only
|
|
49
|
+
/// used to form a slice whose length we return — never dereferenced beyond
|
|
50
|
+
/// NUL.
|
|
51
|
+
///
|
|
52
|
+
/// # Safety
|
|
53
|
+
/// `data` must be a valid NUL-terminated C string for reads up to its terminator.
|
|
54
|
+
#[no_mangle]
|
|
55
|
+
pub unsafe extern "C" fn ffi_probe_echo_cstr(data: *const std::os::raw::c_char) -> usize {
|
|
56
|
+
if data.is_null() {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
std::ffi::CStr::from_ptr(data).to_bytes().len()
|
|
60
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// rust/ffi/rate_limit.rs — rate-limiter C-ABI exports.
|
|
2
|
+
//
|
|
3
|
+
// Both entries check a limit via the opaque `RateLimiter` handle; one hashes a
|
|
4
|
+
// `cstring` key in-engine, the other takes a pre-hashed i64 key. The packed
|
|
5
|
+
// verdict is written by the shared `write_rate_check` helper in `util.rs`.
|
|
6
|
+
|
|
7
|
+
use super::util::{panic_guard, write_rate_check};
|
|
8
|
+
|
|
9
|
+
/// RateLimiter: check a rate limit for a STRING key (hashed internally) at
|
|
10
|
+
/// `now_ms` via its opaque inner handle. Writes packed `[u8 allowed][u32
|
|
11
|
+
/// remaining LE][i64 reset_ms LE]` (13 bytes); needed-size convention; 0 =
|
|
12
|
+
/// null handle (real error).
|
|
13
|
+
///
|
|
14
|
+
/// `key` is a `bun:ffi` `cstring` ARG — the engine transcodes the JS rate-limit
|
|
15
|
+
/// key in-engine (JS does zero encode; the callee borrows via `CStr::from_ptr`).
|
|
16
|
+
/// Intended keys are IPs / route identifiers (text, no NUL); a key containing a
|
|
17
|
+
/// NUL would be truncated before hashing.
|
|
18
|
+
///
|
|
19
|
+
/// # Safety
|
|
20
|
+
/// `inner` must be a valid `RateLimiter` pointer from `inner_ptr()`, alive for
|
|
21
|
+
/// the call; `key` must be a valid NUL-terminated C string; `out` for writes up
|
|
22
|
+
/// to `out_cap`.
|
|
23
|
+
#[no_mangle]
|
|
24
|
+
pub unsafe extern "C" fn castrum_rate_limiter_check(
|
|
25
|
+
inner: usize,
|
|
26
|
+
key: *const std::os::raw::c_char,
|
|
27
|
+
now_ms: i64,
|
|
28
|
+
out: *mut u8,
|
|
29
|
+
out_cap: usize,
|
|
30
|
+
) -> usize {
|
|
31
|
+
if inner == 0 || key.is_null() || out.is_null() {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
let k = std::ffi::CStr::from_ptr(key).to_bytes();
|
|
35
|
+
let hashed = crate::crypto::hashing::fast_hash_bytes(k);
|
|
36
|
+
let (allowed, remaining, reset_ms) = panic_guard(
|
|
37
|
+
|| unsafe {
|
|
38
|
+
crate::ingress::rate_limit::rate_limiter_check_core(
|
|
39
|
+
inner as *const crate::ingress::rate_limit::RateLimiter,
|
|
40
|
+
hashed,
|
|
41
|
+
now_ms as u64,
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
(false, 0, 0),
|
|
45
|
+
);
|
|
46
|
+
write_rate_check(allowed, remaining, reset_ms, out, out_cap)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// RateLimiter: check a rate limit for a PRE-HASHED i64 key at `now_ms` via its
|
|
50
|
+
/// opaque inner handle (packed `[u8 allowed][u32 remaining LE][i64 reset_ms LE]`).
|
|
51
|
+
///
|
|
52
|
+
/// # Safety
|
|
53
|
+
/// `inner` must be a valid `RateLimiter` pointer from `inner_ptr()`, alive for
|
|
54
|
+
/// the call; `out` for writes up to `out_cap`.
|
|
55
|
+
#[no_mangle]
|
|
56
|
+
pub unsafe extern "C" fn castrum_rate_limiter_check_key(
|
|
57
|
+
inner: usize,
|
|
58
|
+
key: i64,
|
|
59
|
+
now_ms: i64,
|
|
60
|
+
out: *mut u8,
|
|
61
|
+
out_cap: usize,
|
|
62
|
+
) -> usize {
|
|
63
|
+
if inner == 0 || out.is_null() {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
let (allowed, remaining, reset_ms) = panic_guard(
|
|
67
|
+
|| unsafe {
|
|
68
|
+
crate::ingress::rate_limit::rate_limiter_check_core(
|
|
69
|
+
inner as *const crate::ingress::rate_limit::RateLimiter,
|
|
70
|
+
key as u64,
|
|
71
|
+
now_ms as u64,
|
|
72
|
+
)
|
|
73
|
+
},
|
|
74
|
+
(false, 0, 0),
|
|
75
|
+
);
|
|
76
|
+
write_rate_check(allowed, remaining, reset_ms, out, out_cap)
|
|
77
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// rust/ffi/route.rs — per-route native stack C-ABI exports (`castrum_route_*`).
|
|
2
|
+
//
|
|
3
|
+
// The live external wire consumed by `@ignex/native` (`createNativeRoute` →
|
|
4
|
+
// `route-wire.ts` v3): a route descriptor compiles ONCE into a pre-baked
|
|
5
|
+
// `NativeRoute` (rust/ingress/native_route.rs), then each request packs a tiny
|
|
6
|
+
// frame and gets a packed verdict in one call. The handle is an owned
|
|
7
|
+
// `Box<NativeRoute>` (opaque u64); `route_run` follows the needed-size
|
|
8
|
+
// convention (`0` = real error, `> out_cap` = exact required size) so the JS
|
|
9
|
+
// wrapper allocates once and retries at most once. All three are `panic_guard`ed
|
|
10
|
+
// — a panic must never unwind through the C ABI.
|
|
11
|
+
|
|
12
|
+
use std::slice;
|
|
13
|
+
|
|
14
|
+
use super::util::panic_guard;
|
|
15
|
+
|
|
16
|
+
/// Compile a route descriptor into an opaque handle (`0` = failure / null
|
|
17
|
+
/// input / panic). The handle is an owned `Box<NativeRoute>`; release it with
|
|
18
|
+
/// {@link castrum_route_destroy}.
|
|
19
|
+
///
|
|
20
|
+
/// # Safety
|
|
21
|
+
/// `desc` must be valid for reads of `desc_len` bytes.
|
|
22
|
+
#[no_mangle]
|
|
23
|
+
pub unsafe extern "C" fn castrum_route_compile(desc: *const u8, desc_len: usize) -> u64 {
|
|
24
|
+
if desc.is_null() {
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
let bytes = slice::from_raw_parts(desc, desc_len);
|
|
28
|
+
panic_guard(
|
|
29
|
+
|| match crate::ingress::NativeRoute::compile(bytes) {
|
|
30
|
+
Ok(route) => Box::into_raw(Box::new(route)) as u64,
|
|
31
|
+
Err(_) => 0,
|
|
32
|
+
},
|
|
33
|
+
0,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Run a compiled route on one request frame, writing the packed verdict into
|
|
38
|
+
/// `out`. Returns bytes written (`0` = real error / malformed frame / panic;
|
|
39
|
+
/// `> out_cap` = the EXACT required size — allocate once and retry).
|
|
40
|
+
///
|
|
41
|
+
/// # Safety
|
|
42
|
+
/// `handle` must be a live handle from {@link castrum_route_compile} (not yet
|
|
43
|
+
/// destroyed); `frame` valid for `frame_len` reads; `out` valid for `out_cap`
|
|
44
|
+
/// writes. The route is immutable (`&self`, no interior mutability), so
|
|
45
|
+
/// concurrent calls from multiple worker threads are safe.
|
|
46
|
+
#[no_mangle]
|
|
47
|
+
pub unsafe extern "C" fn castrum_route_run(
|
|
48
|
+
handle: u64,
|
|
49
|
+
frame: *const u8,
|
|
50
|
+
frame_len: usize,
|
|
51
|
+
out: *mut u8,
|
|
52
|
+
out_cap: usize,
|
|
53
|
+
) -> usize {
|
|
54
|
+
if handle == 0 || frame.is_null() || out.is_null() {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
let route: &crate::ingress::NativeRoute = &*(handle as *const crate::ingress::NativeRoute);
|
|
58
|
+
let frame_slice = slice::from_raw_parts(frame, frame_len);
|
|
59
|
+
let out_slice = slice::from_raw_parts_mut(out, out_cap);
|
|
60
|
+
// Aliasing insurance (mirrors castrum_ingress_handle_packed): a pooled
|
|
61
|
+
// frame that overlaps `out` must be copied before the shared &/&mut borrow
|
|
62
|
+
// pair (aliased &/&mut is instant UB). The JS wrapper uses distinct pooled
|
|
63
|
+
// buffers, so this is a rare defensive copy.
|
|
64
|
+
let owned_frame;
|
|
65
|
+
let frame_ref: &[u8] = if crate::util::slices_overlap(frame_slice, out_slice) {
|
|
66
|
+
owned_frame = frame_slice.to_vec();
|
|
67
|
+
&owned_frame
|
|
68
|
+
} else {
|
|
69
|
+
frame_slice
|
|
70
|
+
};
|
|
71
|
+
panic_guard(|| route.run(frame_ref, out_slice).unwrap_or(0), 0)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Destroy a compiled route handle (frees the `Box<NativeRoute>`). A null
|
|
75
|
+
/// handle is a no-op; double-destroy is UB and must not be called.
|
|
76
|
+
///
|
|
77
|
+
/// # Safety
|
|
78
|
+
/// `handle` must come from {@link castrum_route_compile} and be destroyed at
|
|
79
|
+
/// most once.
|
|
80
|
+
#[no_mangle]
|
|
81
|
+
pub unsafe extern "C" fn castrum_route_destroy(handle: u64) {
|
|
82
|
+
if handle != 0 {
|
|
83
|
+
drop(Box::from_raw(handle as *mut crate::ingress::NativeRoute));
|
|
84
|
+
}
|
|
85
|
+
}
|
package/rust/ffi/task.rs
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// rust/ffi/task.rs — C-ABI exports for the off-thread task runtime.
|
|
2
|
+
//
|
|
3
|
+
// Eight symbols, all thin: the pool/ring/ops live in `rust/task/`. `submit`
|
|
4
|
+
// COPIES the packed args onto the worker (the JS caller's pooled buffer is
|
|
5
|
+
// released as soon as submit returns), and `drain` follows the repo-wide
|
|
6
|
+
// needed-size convention so JS grows once and retries once.
|
|
7
|
+
|
|
8
|
+
use std::slice;
|
|
9
|
+
|
|
10
|
+
use super::util::panic_guard;
|
|
11
|
+
use crate::task;
|
|
12
|
+
|
|
13
|
+
/// Start the pool with `threads` workers (0 → runtime default).
|
|
14
|
+
/// Returns `0` started, `1` already running, `2` internal error.
|
|
15
|
+
#[no_mangle]
|
|
16
|
+
pub extern "C" fn castrum_task_init(threads: u32) -> u32 {
|
|
17
|
+
panic_guard(|| task::init(Some(threads)), 2)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Submit `op` with `args_len` packed arg bytes, completing under `task_id`.
|
|
21
|
+
/// Returns `1` accepted, `0` rejected (null args with a non-zero length).
|
|
22
|
+
///
|
|
23
|
+
/// # Safety
|
|
24
|
+
/// `args` must be valid for reads of `args_len` bytes.
|
|
25
|
+
#[no_mangle]
|
|
26
|
+
pub unsafe extern "C" fn castrum_task_submit(
|
|
27
|
+
op: u32,
|
|
28
|
+
args: *const u8,
|
|
29
|
+
args_len: usize,
|
|
30
|
+
task_id: usize,
|
|
31
|
+
) -> u32 {
|
|
32
|
+
if args.is_null() && args_len != 0 {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
let owned: Vec<u8> = if args_len == 0 {
|
|
36
|
+
Vec::new()
|
|
37
|
+
} else {
|
|
38
|
+
slice::from_raw_parts(args, args_len).to_vec()
|
|
39
|
+
};
|
|
40
|
+
u32::from(task::submit_op(op, owned, task_id as u64))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Drain finished tasks into `out` (packed `[u32 count][entry…]`).
|
|
44
|
+
/// Returns bytes written, `0` when nothing is pending, or the EXACT required
|
|
45
|
+
/// size when `out` is too small (nothing is consumed in that case).
|
|
46
|
+
///
|
|
47
|
+
/// # Safety
|
|
48
|
+
/// `out` must be valid for writes of `out_cap` bytes.
|
|
49
|
+
#[no_mangle]
|
|
50
|
+
pub unsafe extern "C" fn castrum_task_drain(out: *mut u8, out_cap: usize) -> usize {
|
|
51
|
+
if out.is_null() {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
panic_guard(|| task::drain(slice::from_raw_parts_mut(out, out_cap)), 0)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Completions waiting to be drained.
|
|
58
|
+
#[no_mangle]
|
|
59
|
+
pub extern "C" fn castrum_task_pending() -> u32 {
|
|
60
|
+
task::pending()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Submit an op that writes its result DIRECTLY into `out` (zero-copy output):
|
|
64
|
+
/// the completion body is the 8-byte LE written length, not the data, so JS
|
|
65
|
+
/// resolves a view of the buffer it already owns. A too-small `out` completes
|
|
66
|
+
/// with status 3 (TOO_SMALL) and the exact required size as the body; status 4
|
|
67
|
+
/// (UNSUPPORTED) means the op has no `_into` form and the caller should fall
|
|
68
|
+
/// back to `castrum_task_submit`.
|
|
69
|
+
///
|
|
70
|
+
/// Returns `1` accepted, `0` rejected.
|
|
71
|
+
///
|
|
72
|
+
/// # Safety
|
|
73
|
+
/// `args` must be valid for reads of `args_len` bytes. `out` must be valid for
|
|
74
|
+
/// writes of `out_cap` bytes and must stay alive (and untouched by JS) until the
|
|
75
|
+
/// completion for `task_id` is drained.
|
|
76
|
+
#[no_mangle]
|
|
77
|
+
pub unsafe extern "C" fn castrum_task_submit_out(
|
|
78
|
+
op: u32,
|
|
79
|
+
args: *const u8,
|
|
80
|
+
args_len: usize,
|
|
81
|
+
task_id: usize,
|
|
82
|
+
out: *mut u8,
|
|
83
|
+
out_cap: usize,
|
|
84
|
+
) -> u32 {
|
|
85
|
+
if (args.is_null() && args_len != 0) || out.is_null() || out_cap == 0 {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
let owned: Vec<u8> = if args_len == 0 {
|
|
89
|
+
Vec::new()
|
|
90
|
+
} else {
|
|
91
|
+
slice::from_raw_parts(args, args_len).to_vec()
|
|
92
|
+
};
|
|
93
|
+
u32::from(task::submit_op_into(
|
|
94
|
+
op,
|
|
95
|
+
owned,
|
|
96
|
+
task_id as u64,
|
|
97
|
+
out as usize,
|
|
98
|
+
out_cap,
|
|
99
|
+
))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Mark `task_id` cancelled. Returns `1` when newly marked, `0` otherwise.
|
|
103
|
+
#[no_mangle]
|
|
104
|
+
pub extern "C" fn castrum_task_cancel(task_id: usize) -> u32 {
|
|
105
|
+
u32::from(task::mark_cancelled(task_id as u64))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Submit an op whose PAYLOAD is read IN PLACE from the caller's buffer
|
|
109
|
+
/// (zero-copy INPUT). Only the `hdr_len` header bytes are copied, so a large
|
|
110
|
+
/// argument never crosses the boundary — without this, offloading a big
|
|
111
|
+
/// compression costs a full-size copy on the JS thread and measured WORSE than
|
|
112
|
+
/// the synchronous built-in (docs/RND-CONCURRENCY.md §7b).
|
|
113
|
+
///
|
|
114
|
+
/// Returns `1` accepted, `0` rejected.
|
|
115
|
+
///
|
|
116
|
+
/// # Safety
|
|
117
|
+
/// `hdr` must be valid for reads of `hdr_len` bytes. `data` must be valid for
|
|
118
|
+
/// reads of `data_len` bytes and must stay alive (and unmodified by JS) until
|
|
119
|
+
/// the completion for `task_id` is drained.
|
|
120
|
+
#[no_mangle]
|
|
121
|
+
pub unsafe extern "C" fn castrum_task_submit_slice(
|
|
122
|
+
op: u32,
|
|
123
|
+
hdr: *const u8,
|
|
124
|
+
hdr_len: usize,
|
|
125
|
+
data: *const u8,
|
|
126
|
+
data_len: usize,
|
|
127
|
+
task_id: usize,
|
|
128
|
+
) -> u32 {
|
|
129
|
+
if (hdr.is_null() && hdr_len != 0) || (data.is_null() && data_len != 0) {
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
// The header is small, so copying it keeps the caller free to reuse a
|
|
133
|
+
// scratch buffer; the payload is never copied.
|
|
134
|
+
let owned = if hdr_len == 0 {
|
|
135
|
+
Vec::new()
|
|
136
|
+
} else {
|
|
137
|
+
slice::from_raw_parts(hdr, hdr_len).to_vec()
|
|
138
|
+
};
|
|
139
|
+
u32::from(task::submit_slice_op(
|
|
140
|
+
op,
|
|
141
|
+
owned,
|
|
142
|
+
data as usize,
|
|
143
|
+
data_len,
|
|
144
|
+
task_id as u64,
|
|
145
|
+
))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Register the thread-safe doorbell trampoline address (`0` disables it).
|
|
149
|
+
/// Returns `1` on success.
|
|
150
|
+
///
|
|
151
|
+
/// # Safety
|
|
152
|
+
/// `cb` must be a live `void()` C trampoline that is safe to call from any
|
|
153
|
+
/// thread (a `bun:ffi` `JSCallback({ threadsafe: true }).ptr`).
|
|
154
|
+
#[no_mangle]
|
|
155
|
+
pub extern "C" fn castrum_task_set_doorbell(cb: *const std::os::raw::c_void) -> u32 {
|
|
156
|
+
task::set_doorbell(cb as usize);
|
|
157
|
+
1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Stop the pool and drop pending completions. Returns `0`.
|
|
161
|
+
#[no_mangle]
|
|
162
|
+
pub extern "C" fn castrum_task_shutdown() -> u32 {
|
|
163
|
+
task::shutdown();
|
|
164
|
+
task::completion::clear();
|
|
165
|
+
0
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Configured pool worker count (0 before the pool starts).
|
|
169
|
+
#[no_mangle]
|
|
170
|
+
pub extern "C" fn castrum_task_threads() -> u32 {
|
|
171
|
+
task::threads()
|
|
172
|
+
}
|