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.
Files changed (184) hide show
  1. package/Cargo.lock +2259 -0
  2. package/Cargo.toml +138 -0
  3. package/README.md +6 -0
  4. package/build.rs +5 -0
  5. package/castrum.linux-x64-gnu.node +0 -0
  6. package/dist/index.d.ts +1348 -1295
  7. package/dist/index.js +4307 -3776
  8. package/docs/API.md +1 -1
  9. package/docs/ARCHITECTURE.md +3 -2
  10. package/docs/CASE_STUDY.md +2 -2
  11. package/docs/ENVIRONMENT.md +16 -0
  12. package/docs/FFI_BUN_GUIDE.md +89 -6
  13. package/docs/REPO_MAP.md +2 -2
  14. package/index.ts +19 -25
  15. package/package.json +9 -1
  16. package/rust/crypto/aead.rs +378 -0
  17. package/rust/crypto/argon2.rs +265 -0
  18. package/rust/crypto/base64.rs +420 -0
  19. package/rust/crypto/bcrypt.rs +89 -0
  20. package/rust/crypto/cookie_sign.rs +169 -0
  21. package/rust/crypto/csrf.rs +106 -0
  22. package/rust/crypto/ed25519.rs +215 -0
  23. package/rust/crypto/hashing.rs +140 -0
  24. package/rust/crypto/hmac_sha256.rs +88 -0
  25. package/rust/crypto/jwt/api.rs +286 -0
  26. package/rust/crypto/jwt/mod.rs +22 -0
  27. package/rust/crypto/jwt/tests.rs +312 -0
  28. package/rust/crypto/jwt/token.rs +360 -0
  29. package/rust/crypto/mod.rs +27 -0
  30. package/rust/crypto/pbkdf2.rs +96 -0
  31. package/rust/crypto/random_token.rs +85 -0
  32. package/rust/crypto/session.rs +222 -0
  33. package/rust/ffi/crypto.rs +708 -0
  34. package/rust/ffi/hashing.rs +285 -0
  35. package/rust/ffi/http.rs +752 -0
  36. package/rust/ffi/ingress.rs +303 -0
  37. package/rust/ffi/json.rs +517 -0
  38. package/rust/ffi/jwt.rs +419 -0
  39. package/rust/ffi/metrics.rs +373 -0
  40. package/rust/ffi/mod.rs +68 -0
  41. package/rust/ffi/payload.rs +275 -0
  42. package/rust/ffi/probe.rs +60 -0
  43. package/rust/ffi/rate_limit.rs +77 -0
  44. package/rust/ffi/route.rs +85 -0
  45. package/rust/ffi/tests.rs +2333 -0
  46. package/rust/ffi/util.rs +152 -0
  47. package/rust/ffi/validators.rs +123 -0
  48. package/rust/http/accept.rs +606 -0
  49. package/rust/http/cookie_parser.rs +140 -0
  50. package/rust/http/etag.rs +289 -0
  51. package/rust/http/form.rs +132 -0
  52. package/rust/http/headers.rs +267 -0
  53. package/rust/http/http_date.rs +286 -0
  54. package/rust/http/http_parser.rs +252 -0
  55. package/rust/http/media_type.rs +355 -0
  56. package/rust/http/method.rs +200 -0
  57. package/rust/http/mime_lookup.rs +130 -0
  58. package/rust/http/mod.rs +32 -0
  59. package/rust/http/multipart.rs +685 -0
  60. package/rust/http/query_parser.rs +180 -0
  61. package/rust/http/url_codec.rs +296 -0
  62. package/rust/http/url_join.rs +324 -0
  63. package/rust/ingress/api.rs +355 -0
  64. package/rust/ingress/cors.rs +403 -0
  65. package/rust/ingress/ingress_constants.rs +209 -0
  66. package/rust/ingress/ip_trust.rs +312 -0
  67. package/rust/ingress/mod.rs +48 -0
  68. package/rust/ingress/native_route.rs +779 -0
  69. package/rust/ingress/options.rs +176 -0
  70. package/rust/ingress/output.rs +406 -0
  71. package/rust/ingress/packed.rs +191 -0
  72. package/rust/ingress/pipeline.rs +534 -0
  73. package/rust/ingress/proxy.rs +138 -0
  74. package/rust/ingress/rate_limit.rs +538 -0
  75. package/rust/ingress/terminal.rs +114 -0
  76. package/rust/ingress/tests.rs +566 -0
  77. package/rust/ingress/time.rs +70 -0
  78. package/rust/json/fast_schema/capture.rs +336 -0
  79. package/rust/json/fast_schema/compile.rs +556 -0
  80. package/rust/json/fast_schema/cursor.rs +368 -0
  81. package/rust/json/fast_schema/email.rs +282 -0
  82. package/rust/json/fast_schema/errors.rs +247 -0
  83. package/rust/json/fast_schema/mod.rs +57 -0
  84. package/rust/json/fast_schema/tests.rs +1143 -0
  85. package/rust/json/fast_schema/types.rs +140 -0
  86. package/rust/json/fast_schema/validate.rs +931 -0
  87. package/rust/json/json_ops.rs +121 -0
  88. package/rust/json/json_schema.rs +837 -0
  89. package/rust/json/json_ser.rs +881 -0
  90. package/rust/json/mod.rs +19 -0
  91. package/rust/json/napi_marshal.rs +318 -0
  92. package/rust/json/patch/api.rs +60 -0
  93. package/rust/json/patch/engine.rs +153 -0
  94. package/rust/json/patch/mod.rs +47 -0
  95. package/rust/json/patch/ops.rs +270 -0
  96. package/rust/json/patch/pointer.rs +49 -0
  97. package/rust/json/patch/tests.rs +499 -0
  98. package/rust/lib.rs +72 -0
  99. package/rust/metrics/api.rs +148 -0
  100. package/rust/metrics/mod.rs +21 -0
  101. package/rust/metrics/registry.rs +961 -0
  102. package/rust/panic_safety.rs +40 -0
  103. package/rust/payload/compress.rs +512 -0
  104. package/rust/payload/mod.rs +13 -0
  105. package/rust/payload/sse.rs +214 -0
  106. package/rust/payload/template.rs +253 -0
  107. package/rust/payload/websocket.rs +69 -0
  108. package/rust/payload/ws_frames.rs +354 -0
  109. package/rust/proptest_suite.rs +139 -0
  110. package/rust/selection.rs +95 -0
  111. package/rust/src/bin/native-bench.rs +495 -0
  112. package/rust/test_support.rs +71 -0
  113. package/rust/util/batch/api.rs +266 -0
  114. package/rust/util/batch/core.rs +473 -0
  115. package/rust/util/batch/mod.rs +23 -0
  116. package/rust/util/batch/tests.rs +445 -0
  117. package/rust/util/batch_core.rs +184 -0
  118. package/rust/util/bytes.rs +407 -0
  119. package/rust/util/mod.rs +34 -0
  120. package/rust/util/packed.rs +473 -0
  121. package/rust/util/text.rs +130 -0
  122. package/rust/util/threadpool.rs +128 -0
  123. package/rust/util/validation.rs +419 -0
  124. package/rust-toolchain.toml +17 -0
  125. package/scripts/postinstall.mjs +199 -0
  126. package/src/ingress/constants.ts +1 -1
  127. package/src/ingress/context.ts +2 -2
  128. package/src/ingress/decode/baked-result.ts +13 -13
  129. package/src/ingress/decode/fast-result.ts +17 -17
  130. package/src/ingress/errors.ts +7 -7
  131. package/src/ingress/headers/baked-templates.ts +1 -1
  132. package/src/ingress/headers/fast-templates.ts +3 -3
  133. package/src/ingress/index.ts +1 -1
  134. package/src/ingress/native-route.ts +9 -5
  135. package/src/ingress/options.ts +1 -1
  136. package/src/ingress/packing/gather-raw-headers.ts +1 -1
  137. package/src/ingress/packing/route-wire.ts +1 -5
  138. package/src/ingress/response/error-bodies.ts +5 -5
  139. package/src/ingress/response/terminal.ts +1 -1
  140. package/src/ingress/routes/echo.ts +4 -4
  141. package/src/ingress/routes/fallback.ts +2 -2
  142. package/src/ingress/routes/head.ts +2 -2
  143. package/src/ingress/routes/native.ts +2 -1
  144. package/src/ingress/routes/options.ts +1 -1
  145. package/src/ingress/routes/read.ts +2 -2
  146. package/src/ingress/server.ts +10 -6
  147. package/src/ingress/status.ts +7 -7
  148. package/src/integration/batch.ts +1 -1
  149. package/src/integration/index.ts +9 -9
  150. package/src/integration/pipeline.ts +6 -6
  151. package/src/integration/websocket.ts +2 -2
  152. package/src/loader/index.ts +1 -5
  153. package/src/loader/ops.ts +1 -1
  154. package/src/native/ffi/build/codecs.ts +104 -8
  155. package/src/native/ffi/build/compress.ts +14 -10
  156. package/src/native/ffi/build/instances.ts +40 -3
  157. package/src/native/ffi/build/metrics.ts +224 -0
  158. package/src/native/ffi/build/parse.ts +59 -23
  159. package/src/native/ffi/build/util.ts +14 -1
  160. package/src/native/ffi/build.ts +2 -0
  161. package/src/native/ffi/selftest.ts +10 -3
  162. package/src/native/ffi/types.ts +111 -9
  163. package/src/native/ffi.ts +87 -1
  164. package/src/native/index.ts +1 -1
  165. package/src/native/loader.ts +2 -2
  166. package/src/native/types/instances.ts +34 -0
  167. package/src/native/types.ts +11 -0
  168. package/src/runtime/codec.ts +53 -9
  169. package/src/runtime/types.ts +7 -0
  170. package/src/rust-ffi/batch/build.ts +16 -9
  171. package/src/rust-ffi/index.ts +7 -6
  172. package/src/rust-ffi/options.ts +1 -3
  173. package/src/rust-ffi/proven.ts +3 -3
  174. package/src/rust-ffi/scalar/crypto.ts +1 -1
  175. package/src/rust-ffi/scalar/factories.ts +73 -0
  176. package/src/rust-ffi/scalar/hashing.ts +1 -1
  177. package/src/rust-ffi/scalar/http.ts +17 -11
  178. package/src/rust-ffi/scalar/index.ts +2 -0
  179. package/src/rust-ffi/scalar/interface.ts +20 -0
  180. package/src/rust-ffi/scalar/payload.ts +11 -3
  181. package/src/rust-ffi/scalar/validation.ts +121 -0
  182. package/src/selection.ts +1 -1
  183. package/src/shared/bytes.ts +5 -4
  184. package/src/shared/codec.ts +18 -3
@@ -0,0 +1,191 @@
1
+ // rust/ingress/packed.rs — Packed-input readers + builder.
2
+ //
3
+ // The ingress pipeline consumes a packed metadata frame (method byte +
4
+ // url/ip/rid/headers sections). This module owns the low-level section readers
5
+ // and the builder that assembles the full frame from raw request components
6
+ // (used by the `full_sync` family).
7
+ //
8
+ // Pure core (no napi types): errors are plain `String` messages; the napi
9
+ // boundary maps them to terminal responses / JS errors.
10
+
11
+ /// Read a u32le length prefix, advancing `pos` on success.
12
+ #[inline]
13
+ pub(crate) fn read_u32_at(input: &[u8], pos: &mut usize) -> std::result::Result<usize, String> {
14
+ if *pos + 4 > input.len() {
15
+ return Err("packed input: truncated u32".to_string());
16
+ }
17
+ let v = u32::from_le_bytes([
18
+ input[*pos],
19
+ input[*pos + 1],
20
+ input[*pos + 2],
21
+ input[*pos + 3],
22
+ ]) as usize;
23
+ *pos += 4;
24
+ Ok(v)
25
+ }
26
+
27
+ /// Read a length-prefixed section, enforcing `max` and bounds.
28
+ #[inline]
29
+ pub(crate) fn read_section<'a>(
30
+ input: &'a [u8],
31
+ pos: &mut usize,
32
+ max: usize,
33
+ ) -> std::result::Result<&'a [u8], String> {
34
+ let len = read_u32_at(input, pos)?;
35
+ if len > max {
36
+ return Err("packed input: section too large".to_string());
37
+ }
38
+ let end = pos
39
+ .checked_add(len)
40
+ .ok_or_else(|| "packed input: length overflow".to_string())?;
41
+ if end > input.len() {
42
+ return Err("packed input: truncated section".to_string());
43
+ }
44
+ let slice = &input[*pos..end];
45
+ *pos = end;
46
+ Ok(slice)
47
+ }
48
+
49
+ /// Build the full packed input buffer from raw components (sync version).
50
+ pub(crate) fn build_packed_input_sync(
51
+ method_kind: u8,
52
+ url_bytes: &[u8],
53
+ ip_bytes: &[u8],
54
+ rid_bytes: &[u8],
55
+ headers: &[(String, String)],
56
+ ) -> Vec<u8> {
57
+ let mut buf = Vec::with_capacity(512);
58
+
59
+ // Method kind (1 byte)
60
+ buf.push(method_kind);
61
+
62
+ // URL section: u32le length-prefixed
63
+ buf.extend_from_slice(&(url_bytes.len() as u32).to_le_bytes());
64
+ buf.extend_from_slice(url_bytes);
65
+
66
+ // IP section: u32le length-prefixed
67
+ buf.extend_from_slice(&(ip_bytes.len() as u32).to_le_bytes());
68
+ buf.extend_from_slice(ip_bytes);
69
+
70
+ // Request ID section: u32le length-prefixed
71
+ buf.extend_from_slice(&(rid_bytes.len() as u32).to_le_bytes());
72
+ buf.extend_from_slice(rid_bytes);
73
+
74
+ // Headers section: u32le length-prefixed
75
+ let headers_len_pos = buf.len();
76
+ buf.extend_from_slice(&0u32.to_le_bytes()); // placeholder
77
+ let headers_start = buf.len();
78
+
79
+ // Write header count (u16le)
80
+ buf.extend_from_slice(&(headers.len() as u16).to_le_bytes());
81
+
82
+ for (name, value) in headers {
83
+ let name_bytes = name.as_bytes();
84
+ buf.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
85
+ buf.extend_from_slice(name_bytes);
86
+ let value_bytes = value.as_bytes();
87
+ buf.extend_from_slice(&(value_bytes.len() as u32).to_le_bytes());
88
+ buf.extend_from_slice(value_bytes);
89
+ }
90
+
91
+ // Patch the header section length
92
+ let headers_len = buf.len() - headers_start;
93
+ buf[headers_len_pos..headers_len_pos + 4].copy_from_slice(&(headers_len as u32).to_le_bytes());
94
+
95
+ buf
96
+ }
97
+
98
+ #[cfg(test)]
99
+ mod tests {
100
+ use super::*;
101
+
102
+ #[test]
103
+ fn build_packed_input_sync_round_trips() {
104
+ let headers = vec![
105
+ ("cookie".to_string(), "a=1".to_string()),
106
+ ("x-forwarded-for".to_string(), "1.2.3.4".to_string()),
107
+ ];
108
+ let frame =
109
+ build_packed_input_sync(3, b"/api/users?a=1", b"127.0.0.1", b"rid-123", &headers);
110
+
111
+ // Method kind byte.
112
+ assert_eq!(frame[0], 3);
113
+
114
+ let mut pos = 1;
115
+
116
+ // URL section.
117
+ assert_eq!(
118
+ read_section(&frame, &mut pos, 8192).unwrap(),
119
+ b"/api/users?a=1"
120
+ );
121
+ // IP section.
122
+ assert_eq!(read_section(&frame, &mut pos, 128).unwrap(), b"127.0.0.1");
123
+ // Request-ID section.
124
+ assert_eq!(read_section(&frame, &mut pos, 256).unwrap(), b"rid-123");
125
+
126
+ // Headers section: u16 count, then {u16 name_len, name, u32 val_len, value}.
127
+ let hdrs = read_section(&frame, &mut pos, 65536).unwrap();
128
+ let mut hp = 0usize;
129
+ let count = u16::from_le_bytes([hdrs[hp], hdrs[hp + 1]]);
130
+ hp += 2;
131
+ assert_eq!(count, 2);
132
+
133
+ let mut pairs = Vec::new();
134
+ for _ in 0..count {
135
+ let name_len = u16::from_le_bytes([hdrs[hp], hdrs[hp + 1]]) as usize;
136
+ hp += 2;
137
+ let name = std::str::from_utf8(&hdrs[hp..hp + name_len]).unwrap();
138
+ hp += name_len;
139
+ let val_len = u32::from_le_bytes(hdrs[hp..hp + 4].try_into().unwrap()) as usize;
140
+ hp += 4;
141
+ let value = std::str::from_utf8(&hdrs[hp..hp + val_len]).unwrap();
142
+ hp += val_len;
143
+ pairs.push((name.to_string(), value.to_string()));
144
+ }
145
+ assert_eq!(pairs, headers);
146
+ assert_eq!(hp, hdrs.len(), "headers section fully consumed");
147
+ assert_eq!(pos, frame.len(), "whole frame fully consumed");
148
+ }
149
+
150
+ #[test]
151
+ fn build_packed_input_sync_empty_headers() {
152
+ let frame = build_packed_input_sync(1, b"/", b"", b"", &[]);
153
+ let mut pos = 1;
154
+ assert_eq!(read_section(&frame, &mut pos, 8192).unwrap(), b"/");
155
+ assert_eq!(read_section(&frame, &mut pos, 128).unwrap(), b"");
156
+ assert_eq!(read_section(&frame, &mut pos, 256).unwrap(), b"");
157
+ let hdrs = read_section(&frame, &mut pos, 65536).unwrap();
158
+ assert_eq!(hdrs.len(), 2, "empty headers section is just the u16 count");
159
+ assert_eq!(u16::from_le_bytes([hdrs[0], hdrs[1]]), 0);
160
+ assert_eq!(pos, frame.len());
161
+ }
162
+
163
+ #[test]
164
+ fn read_u32_at_rejects_truncated() {
165
+ let mut pos = 0;
166
+ assert!(read_u32_at(&[0, 1, 2], &mut pos).is_err());
167
+ assert_eq!(pos, 0, "pos must not advance on error");
168
+ }
169
+
170
+ #[test]
171
+ fn read_section_enforces_max_and_bounds() {
172
+ // Declared length exceeds the max.
173
+ let input = [8u8, 0, 0, 0, 1, 2, 3, 4];
174
+ let mut pos = 0;
175
+ assert!(read_section(&input, &mut pos, 4).is_err());
176
+
177
+ // Declared length exceeds the buffer (truncated).
178
+ let input2 = [100u8, 0, 0, 0, 1, 2, 3, 4];
179
+ let mut pos2 = 0;
180
+ assert!(read_section(&input2, &mut pos2, 200).is_err());
181
+ }
182
+
183
+ #[test]
184
+ fn read_section_rejects_length_overflow() {
185
+ // Near-u32::MAX declared length: `pos + len` must not wrap and must be
186
+ // rejected as truncated (not panic).
187
+ let input = [0xff, 0xff, 0xff, 0x7f];
188
+ let mut pos = 0;
189
+ assert!(read_section(&input, &mut pos, usize::MAX).is_err());
190
+ }
191
+ }