castrum 0.9.6 → 0.9.8
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 +4 -3
- package/Cargo.toml +1 -1
- 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 +501 -5
- package/dist/index.js +1658 -1314
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/BENCHMARKS.md +13 -0
- package/docs/CASE_STUDY.md +2 -2
- package/docs/ENVIRONMENT.md +1 -1
- package/docs/FFI_BUN_GUIDE.md +13 -1
- package/docs/HOW_TO_ADD_AN_OP.md +24 -0
- package/docs/INGRESS.md +4 -4
- package/docs/REPO_MAP.md +8 -6
- package/docs/superpowers/plans/2026-09-18-castrum-hardening-cleanup.md +617 -0
- package/index.ts +1 -0
- package/package.json +3 -2
- package/rust/crypto/aead.rs +44 -0
- package/rust/crypto/argon2.rs +32 -2
- package/rust/crypto/jwt/tests.rs +19 -0
- package/rust/crypto/jwt/token.rs +43 -28
- package/rust/crypto/session.rs +170 -7
- package/rust/ffi/crypto.rs +182 -61
- package/rust/ffi/hashing.rs +48 -33
- package/rust/ffi/http.rs +62 -36
- package/rust/ffi/metrics.rs +6 -2
- package/rust/ffi/payload.rs +47 -27
- package/rust/ffi/task.rs +51 -32
- package/rust/ffi/tests.rs +197 -0
- package/rust/ffi/util.rs +40 -0
- package/rust/http/headers.rs +13 -0
- package/rust/http/multipart.rs +139 -0
- package/rust/ingress/native_route.rs +21 -3
- package/rust/task/completion.rs +7 -0
- package/rust/task/mod.rs +1 -1
- package/rust/task/tests.rs +30 -0
- package/src/ingress/decode/baked-result.ts +13 -5
- package/src/ingress/decode/fast-result.ts +1 -1
- package/src/ingress/decode/result-base.ts +16 -2
- package/src/ingress/handlers.ts +103 -31
- package/src/ingress/headers/memoized-headers.ts +40 -0
- package/src/ingress/native-route.ts +25 -6
- package/src/ingress/options.ts +26 -2
- package/src/ingress/packing/gather-raw-headers.ts +24 -8
- package/src/ingress/response/baked-response.ts +118 -28
- package/src/ingress/router.ts +14 -2
- package/src/ingress/routes/common.ts +26 -10
- package/src/ingress/routes/echo.ts +50 -7
- package/src/ingress/routes/json-write.ts +21 -8
- package/src/ingress/routes/responder.ts +4 -4
- package/src/ingress/server-node.ts +33 -14
- package/src/ingress/server.ts +3 -4
- package/src/ingress/shared.ts +1 -1
- package/src/ingress/types.ts +15 -0
- package/src/loader/ops.ts +1 -1
- package/src/native/ffi/build/codecs-selftest.ts +305 -0
- package/src/native/ffi/build/codecs.ts +0 -296
- package/src/native/ffi/build/instances-selftest.ts +264 -0
- package/src/native/ffi/build/instances.ts +39 -241
- package/src/native/ffi/build/metrics.ts +0 -7
- package/src/native/ffi/build/parse-selftest.ts +238 -0
- package/src/native/ffi/build/parse.ts +11 -234
- package/src/native/ffi/build.ts +5 -1
- package/src/native/ffi/selftest.ts +3 -3
- package/src/native/ffi/symbols.ts +460 -0
- package/src/native/ffi/types.ts +17 -0
- package/src/native/ffi.ts +2 -422
- package/src/native/ingress-binding.ts +39 -0
- package/src/runtime/codec.ts +24 -0
- package/src/runtime/types.ts +11 -0
- package/src/rust-ffi/batch/build.ts +18 -8
- package/src/rust-ffi/batch/types.ts +15 -0
- package/src/rust-ffi/options.ts +19 -0
- package/src/rust-ffi/scalar/crypto.ts +8 -0
- package/src/rust-ffi/scalar/factories.ts +47 -20
- package/src/rust-ffi/scalar/http.ts +60 -10
- package/src/rust-ffi/scalar/interface.ts +4 -1
- package/src/rust-ffi/scalar/payload.ts +27 -1
- package/src/shared/buffer-pool.ts +44 -9
- package/src/shared/codec.ts +26 -0
- package/src/shared/packed/parsers.ts +64 -7
- package/src/task/runtime.ts +29 -28
package/src/task/runtime.ts
CHANGED
|
@@ -75,11 +75,7 @@ export interface TaskRuntime {
|
|
|
75
75
|
/** gzip-compress `data` on a pool thread. */
|
|
76
76
|
gzipCompress(data: Uint8Array, options?: TaskRunOptions): Promise<Uint8Array>
|
|
77
77
|
/** Verify a password against a PHC string on a pool thread (10-200 ms CPU). */
|
|
78
|
-
argon2Verify(
|
|
79
|
-
password: Uint8Array,
|
|
80
|
-
phc: Uint8Array,
|
|
81
|
-
options?: TaskRunOptions,
|
|
82
|
-
): Promise<boolean>
|
|
78
|
+
argon2Verify(password: Uint8Array, phc: Uint8Array, options?: TaskRunOptions): Promise<boolean>
|
|
83
79
|
/** PBKDF2-HMAC-SHA256 on a pool thread (10-200 ms CPU, 1-64 B output). */
|
|
84
80
|
pbkdf2Sha256(
|
|
85
81
|
password: Uint8Array,
|
|
@@ -345,9 +341,7 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
345
341
|
} else if (status === TASK_STATUS_CANCELLED) {
|
|
346
342
|
deferred.reject(cancelledError())
|
|
347
343
|
} else {
|
|
348
|
-
deferred.reject(
|
|
349
|
-
new Error(decodeUtf8(bodyOf(buf, bodyOff, len, pooled)) || 'task failed'),
|
|
350
|
-
)
|
|
344
|
+
deferred.reject(new Error(decodeUtf8(bodyOf(buf, bodyOff, len, pooled)) || 'task failed'))
|
|
351
345
|
}
|
|
352
346
|
completed++
|
|
353
347
|
}
|
|
@@ -361,11 +355,8 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
361
355
|
queueMicrotask(drainNow)
|
|
362
356
|
}
|
|
363
357
|
|
|
364
|
-
function submit(
|
|
365
|
-
|
|
366
|
-
args: Uint8Array,
|
|
367
|
-
runOptions?: TaskRunOptions,
|
|
368
|
-
): Promise<Uint8Array> {
|
|
358
|
+
function submit(op: number, args: Uint8Array, runOptions?: TaskRunOptions): Promise<Uint8Array> {
|
|
359
|
+
ensureDoorbell()
|
|
369
360
|
const id = nextId++
|
|
370
361
|
return new Promise<Uint8Array>((resolve, reject) => {
|
|
371
362
|
inflight.set(id, { out: null, keep: null, resolve, reject })
|
|
@@ -398,6 +389,7 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
398
389
|
output: Uint8Array,
|
|
399
390
|
runOptions?: TaskRunOptions,
|
|
400
391
|
): Promise<Uint8Array> {
|
|
392
|
+
ensureDoorbell()
|
|
401
393
|
const id = nextId++
|
|
402
394
|
return new Promise<Uint8Array>((resolve, reject) => {
|
|
403
395
|
inflight.set(id, { out: output, keep: null, resolve, reject })
|
|
@@ -431,6 +423,7 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
431
423
|
data: Uint8Array,
|
|
432
424
|
runOptions?: TaskRunOptions,
|
|
433
425
|
): Promise<Uint8Array> {
|
|
426
|
+
ensureDoorbell()
|
|
434
427
|
const id = nextId++
|
|
435
428
|
return new Promise<Uint8Array>((resolve, reject) => {
|
|
436
429
|
inflight.set(id, { out: null, keep: data, resolve, reject })
|
|
@@ -453,19 +446,26 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
453
446
|
}
|
|
454
447
|
|
|
455
448
|
// One process-wide trampoline; Bun marshals calls from pool threads onto the
|
|
456
|
-
// JS thread. Rust coalesces so a burst rings it once.
|
|
449
|
+
// JS thread. Rust coalesces so a burst rings it once. Created lazily (and
|
|
450
|
+
// re-created after a shutdown) so the native side NEVER holds the address of
|
|
451
|
+
// a closed `JSCallback` — calling a freed trampoline is a use-after-free.
|
|
457
452
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
458
453
|
const { JSCallback } = require('bun:ffi') as typeof import('bun:ffi')
|
|
459
|
-
|
|
460
|
-
()
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
454
|
+
function ensureDoorbell(): void {
|
|
455
|
+
if (doorbell !== null) return
|
|
456
|
+
const cb = new JSCallback(
|
|
457
|
+
() => {
|
|
458
|
+
drainNow()
|
|
459
|
+
},
|
|
460
|
+
{ args: [], returns: 'void', threadsafe: true } as unknown as ConstructorParameters<
|
|
461
|
+
typeof JSCallback
|
|
462
|
+
>[1],
|
|
463
|
+
)
|
|
464
|
+
doorbell = cb
|
|
465
|
+
ffi.taskSetDoorbell(Number(cb.ptr))
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
ensureDoorbell()
|
|
469
469
|
|
|
470
470
|
// A zero-copy result handoff via `toArrayBuffer(ptr, 0, len, dealloc)` was
|
|
471
471
|
// prototyped here and REVERTED: Bun 1.4.2's deallocator hook segfaults
|
|
@@ -492,8 +492,7 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
492
492
|
// UNSUPPORTED from an older addon) fall back, so behavior can't regress.
|
|
493
493
|
// Args are re-submitted on the retry step, so they own their memory.
|
|
494
494
|
const args = encodeGzipDecompressArgs(data, runOptions?.maxDecompressed)
|
|
495
|
-
const copyPath = (): Promise<Uint8Array> =>
|
|
496
|
-
submit(TASK_OP.gzipDecompress, args, runOptions)
|
|
495
|
+
const copyPath = (): Promise<Uint8Array> => submit(TASK_OP.gzipDecompress, args, runOptions)
|
|
497
496
|
const zeroCopyOnce = (cap: number): Promise<Uint8Array> =>
|
|
498
497
|
submitInto(TASK_OP.gzipDecompressInto, args, Buffer.allocUnsafe(cap), runOptions)
|
|
499
498
|
return zeroCopyOnce(size)
|
|
@@ -521,8 +520,7 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
521
520
|
},
|
|
522
521
|
brotliDecompress(data, runOptions) {
|
|
523
522
|
const args = encodeBrotliDecompressArgs(data, runOptions?.maxDecompressed)
|
|
524
|
-
const copyPath = (): Promise<Uint8Array> =>
|
|
525
|
-
submit(TASK_OP.brotliDecompress, args, runOptions)
|
|
523
|
+
const copyPath = (): Promise<Uint8Array> => submit(TASK_OP.brotliDecompress, args, runOptions)
|
|
526
524
|
// Brotli has no ISIZE trailer, so there is no exact size hint: start from
|
|
527
525
|
// a ratio guess and let the needed-size retry land it exactly (at most
|
|
528
526
|
// one extra pass; on anything but TOO_SMALL, fall back to the copy op).
|
|
@@ -557,6 +555,9 @@ export function createTaskRuntime(options: TaskRuntimeOptions = {}): TaskRuntime
|
|
|
557
555
|
deferred.reject(cancelledError())
|
|
558
556
|
}
|
|
559
557
|
inflight.clear()
|
|
558
|
+
// Zero the native doorbell BEFORE closing the JS trampoline: a worker
|
|
559
|
+
// completing after this point must never call a freed callback.
|
|
560
|
+
ffi.taskSetDoorbell(0)
|
|
560
561
|
ffi.taskShutdown()
|
|
561
562
|
release()
|
|
562
563
|
doorbell?.close()
|