castrum 0.7.0

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 (86) hide show
  1. package/Cargo.toml +73 -0
  2. package/README.md +432 -0
  3. package/bench.ts +3 -0
  4. package/castrum.linux-x64-gnu.node +0 -0
  5. package/index.ts +24 -0
  6. package/package.json +94 -0
  7. package/rust/batch.rs +195 -0
  8. package/rust/cookie_parser.rs +70 -0
  9. package/rust/cors.rs +263 -0
  10. package/rust/hashing.rs +164 -0
  11. package/rust/headers.rs +234 -0
  12. package/rust/hmac_sha256.rs +113 -0
  13. package/rust/http_parser.rs +132 -0
  14. package/rust/ingress.rs +616 -0
  15. package/rust/ingress_constants.rs +89 -0
  16. package/rust/ip_trust.rs +185 -0
  17. package/rust/json_ops.rs +66 -0
  18. package/rust/json_patch_ops.rs +25 -0
  19. package/rust/json_schema.rs +151 -0
  20. package/rust/json_ser.rs +429 -0
  21. package/rust/lib.rs +48 -0
  22. package/rust/method.rs +141 -0
  23. package/rust/mime_lookup.rs +76 -0
  24. package/rust/output.rs +238 -0
  25. package/rust/proxy.rs +90 -0
  26. package/rust/query_parser.rs +141 -0
  27. package/rust/random_token.rs +52 -0
  28. package/rust/rate_limit.rs +203 -0
  29. package/rust/terminal.rs +95 -0
  30. package/rust/unit_tests.rs +843 -0
  31. package/rust/url_codec.rs +133 -0
  32. package/rust/util.rs +537 -0
  33. package/rust/validation.rs +107 -0
  34. package/rust/websocket.rs +23 -0
  35. package/src/baseline/index.ts +12 -0
  36. package/src/baseline/tasks/cookie.ts +22 -0
  37. package/src/baseline/tasks/hashing.ts +18 -0
  38. package/src/baseline/tasks/hmac.ts +38 -0
  39. package/src/baseline/tasks/http.ts +62 -0
  40. package/src/baseline/tasks/json-patch.ts +12 -0
  41. package/src/baseline/tasks/json.ts +50 -0
  42. package/src/baseline/tasks/mime.ts +5 -0
  43. package/src/baseline/tasks/query.ts +14 -0
  44. package/src/baseline/tasks/token.ts +15 -0
  45. package/src/baseline/tasks/url.ts +11 -0
  46. package/src/baseline/tasks/validation.ts +19 -0
  47. package/src/baseline/tasks/websocket.ts +14 -0
  48. package/src/bench/assert.ts +70 -0
  49. package/src/bench/checks.ts +227 -0
  50. package/src/bench/checksum.ts +27 -0
  51. package/src/bench/comparisons.ts +70 -0
  52. package/src/bench/concurrent-worker.ts +135 -0
  53. package/src/bench/fixtures.ts +233 -0
  54. package/src/bench/index.ts +2 -0
  55. package/src/bench/measure.ts +177 -0
  56. package/src/bench/now.ts +3 -0
  57. package/src/bench/report.ts +79 -0
  58. package/src/bench/run.ts +57 -0
  59. package/src/bench/tasks/complex.ts +232 -0
  60. package/src/bench/tasks/concurrent.ts +213 -0
  61. package/src/bench/tasks/cookie.ts +44 -0
  62. package/src/bench/tasks/hashing.ts +33 -0
  63. package/src/bench/tasks/hmac.ts +36 -0
  64. package/src/bench/tasks/http.ts +50 -0
  65. package/src/bench/tasks/index.ts +52 -0
  66. package/src/bench/tasks/json-patch.ts +21 -0
  67. package/src/bench/tasks/json.ts +33 -0
  68. package/src/bench/tasks/mime.ts +21 -0
  69. package/src/bench/tasks/query.ts +44 -0
  70. package/src/bench/tasks/stress.ts +142 -0
  71. package/src/bench/tasks/token.ts +20 -0
  72. package/src/bench/tasks/url.ts +34 -0
  73. package/src/bench/tasks/validation.ts +57 -0
  74. package/src/bench/tasks/websocket.ts +21 -0
  75. package/src/bench/types.ts +60 -0
  76. package/src/data/json-rows.ts +33 -0
  77. package/src/ingress/constants.ts +56 -0
  78. package/src/ingress/fast.ts +1082 -0
  79. package/src/ingress/handlers.ts +1202 -0
  80. package/src/ingress/index.ts +457 -0
  81. package/src/ingress/packed-input.ts +59 -0
  82. package/src/native/index.ts +207 -0
  83. package/src/rust-ffi/index.ts +555 -0
  84. package/src/shared/bytes.ts +36 -0
  85. package/src/shared/json.ts +18 -0
  86. package/src/shared/packed.ts +316 -0
@@ -0,0 +1,316 @@
1
+ import { decoder, encoder } from "./bytes";
2
+ import addon from "../native";
3
+ import type { SchemaValidatorInstance } from "../native";
4
+
5
+ export type SchemaValidator = SchemaValidatorInstance;
6
+
7
+ function dataView(bytes: Uint8Array): DataView {
8
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9
+ }
10
+
11
+ function readU32(dv: DataView, offset: number): number {
12
+ if (offset + 4 > dv.byteLength) {
13
+ throw new RangeError("packed buffer: truncated u32");
14
+ }
15
+
16
+ return dv.getUint32(offset, true);
17
+ }
18
+
19
+ function readSlice(bytes: Uint8Array, offset: number, len: number): Uint8Array {
20
+ if (len < 0 || offset + len > bytes.byteLength) {
21
+ throw new RangeError("packed buffer: truncated bytes");
22
+ }
23
+
24
+ return bytes.subarray(offset, offset + len);
25
+ }
26
+
27
+ export function unpackU32Array(bytes: Uint8Array): Uint32Array {
28
+ if (bytes.byteLength < 4) {
29
+ return new Uint32Array(0);
30
+ }
31
+
32
+ const dv = dataView(bytes);
33
+ const count = readU32(dv, 0);
34
+
35
+ if (count > Math.floor((bytes.byteLength - 4) / 4)) {
36
+ throw new RangeError("packed buffer: invalid u32 count");
37
+ }
38
+
39
+ const out = new Uint32Array(count);
40
+
41
+ for (let i = 0; i < count; i++) {
42
+ out[i] = readU32(dv, 4 + i * 4);
43
+ }
44
+
45
+ return out;
46
+ }
47
+
48
+ export function unpackBitset(bytes: Uint8Array): Uint8Array {
49
+ if (bytes.byteLength < 4) {
50
+ return new Uint8Array(0);
51
+ }
52
+
53
+ const dv = dataView(bytes);
54
+ const count = readU32(dv, 0);
55
+ const expectedBytes = Math.ceil(count / 8);
56
+
57
+ if (bytes.byteLength < 4 + expectedBytes) {
58
+ throw new RangeError("packed buffer: truncated bitset");
59
+ }
60
+
61
+ const bits = bytes.subarray(4);
62
+ const out = new Uint8Array(count);
63
+
64
+ for (let i = 0; i < count; i++) {
65
+ const byte = bits[i >> 3] ?? 0;
66
+ out[i] = (byte >> (i & 7)) & 1;
67
+ }
68
+
69
+ return out;
70
+ }
71
+
72
+ export function unpackI64ArrayAsBigInt(bytes: Uint8Array): BigInt64Array {
73
+ if (bytes.byteLength < 4) {
74
+ return new BigInt64Array(0);
75
+ }
76
+
77
+ const dv = dataView(bytes);
78
+ const count = readU32(dv, 0);
79
+
80
+ if (count > Math.floor((bytes.byteLength - 4) / 8)) {
81
+ throw new RangeError("packed buffer: invalid i64 count");
82
+ }
83
+
84
+ const out = new BigInt64Array(count);
85
+
86
+ for (let i = 0; i < count; i++) {
87
+ out[i] = dv.getBigInt64(4 + i * 8, true);
88
+ }
89
+
90
+ return out;
91
+ }
92
+
93
+ export function unpackByteResults(bytes: Uint8Array): Uint8Array[] {
94
+ if (bytes.byteLength < 4) {
95
+ return [];
96
+ }
97
+
98
+ const dv = dataView(bytes);
99
+ const count = readU32(dv, 0);
100
+
101
+ const out: Uint8Array[] = [];
102
+ let offset = 4;
103
+
104
+ for (let i = 0; i < count; i++) {
105
+ const len = readU32(dv, offset);
106
+ offset += 4;
107
+
108
+ const slice = readSlice(bytes, offset, len);
109
+ offset += len;
110
+
111
+ out.push(slice);
112
+ }
113
+
114
+ return out;
115
+ }
116
+
117
+ export type Pair = [string, string];
118
+
119
+ export function readPairsPacked(bytes: Uint8Array): Pair[] {
120
+ if (bytes.byteLength < 4) {
121
+ return [];
122
+ }
123
+
124
+ const dv = dataView(bytes);
125
+ const count = readU32(dv, 0);
126
+
127
+ if (count === 0) {
128
+ return [];
129
+ }
130
+
131
+ const out: Pair[] = [];
132
+ let offset = 4;
133
+
134
+ for (let i = 0; i < count; i++) {
135
+ const keyLen = readU32(dv, offset);
136
+ offset += 4;
137
+
138
+ const keyBytes = readSlice(bytes, offset, keyLen);
139
+ offset += keyLen;
140
+
141
+ const valueLen = readU32(dv, offset);
142
+ offset += 4;
143
+
144
+ const valueBytes = readSlice(bytes, offset, valueLen);
145
+ offset += valueLen;
146
+
147
+ out.push([decoder.decode(keyBytes), decoder.decode(valueBytes)]);
148
+ }
149
+
150
+ return out;
151
+ }
152
+
153
+ export function pairsToObject(
154
+ pairs: Pair[],
155
+ ): Record<string, string | string[]> {
156
+ const out: Record<string, string | string[]> = {};
157
+
158
+ for (const [key, value] of pairs) {
159
+ const existing = out[key];
160
+
161
+ if (existing === undefined) {
162
+ out[key] = value;
163
+ } else if (Array.isArray(existing)) {
164
+ existing.push(value);
165
+ } else {
166
+ out[key] = [existing, value];
167
+ }
168
+ }
169
+
170
+ return out;
171
+ }
172
+
173
+ export interface ParsedHttpRequestPacked {
174
+ method: string;
175
+ path: string;
176
+ version: string;
177
+ headers: Record<string, string>;
178
+ }
179
+
180
+ export function readHttpPacked(bytes: Uint8Array): ParsedHttpRequestPacked {
181
+ const dv = dataView(bytes);
182
+ let offset = 0;
183
+
184
+ const methodLen = readU32(dv, offset);
185
+ offset += 4;
186
+ const method = decoder.decode(readSlice(bytes, offset, methodLen));
187
+ offset += methodLen;
188
+
189
+ const pathLen = readU32(dv, offset);
190
+ offset += 4;
191
+ const path = decoder.decode(readSlice(bytes, offset, pathLen));
192
+ offset += pathLen;
193
+
194
+ const versionLen = readU32(dv, offset);
195
+ offset += 4;
196
+ const version = decoder.decode(readSlice(bytes, offset, versionLen));
197
+ offset += versionLen;
198
+
199
+ const headerCount = readU32(dv, offset);
200
+ offset += 4;
201
+
202
+ const headers: Record<string, string> = {};
203
+
204
+ for (let i = 0; i < headerCount; i++) {
205
+ const nameLen = readU32(dv, offset);
206
+ offset += 4;
207
+ const name = decoder.decode(readSlice(bytes, offset, nameLen));
208
+ offset += nameLen;
209
+
210
+ const valueLen = readU32(dv, offset);
211
+ offset += 4;
212
+ const value = decoder.decode(readSlice(bytes, offset, valueLen));
213
+ offset += valueLen;
214
+
215
+ headers[name] = value;
216
+ }
217
+
218
+ return {
219
+ method,
220
+ path,
221
+ version,
222
+ headers,
223
+ };
224
+ }
225
+
226
+ export function packBatch(items: Uint8Array[]): Uint8Array {
227
+ let total = 4;
228
+
229
+ for (const item of items) {
230
+ total += 4 + item.byteLength;
231
+ }
232
+
233
+ const out = new Uint8Array(total);
234
+ const dv = new DataView(out.buffer);
235
+
236
+ dv.setUint32(0, items.length, true);
237
+
238
+ let offset = 4;
239
+
240
+ for (const item of items) {
241
+ dv.setUint32(offset, item.byteLength, true);
242
+ offset += 4;
243
+
244
+ out.set(item, offset);
245
+ offset += item.byteLength;
246
+ }
247
+
248
+ return out;
249
+ }
250
+
251
+ export function packPairs(pairs: Array<[string, string]>): Uint8Array {
252
+ const encoded = pairs.map(
253
+ ([key, value]) => [encoder.encode(key), encoder.encode(value)] as const,
254
+ );
255
+
256
+ let total = 4;
257
+
258
+ for (const [key, value] of encoded) {
259
+ total += 8 + key.byteLength + value.byteLength;
260
+ }
261
+
262
+ const out = new Uint8Array(total);
263
+ const dv = new DataView(out.buffer);
264
+
265
+ dv.setUint32(0, encoded.length, true);
266
+
267
+ let offset = 4;
268
+
269
+ for (const [key, value] of encoded) {
270
+ dv.setUint32(offset, key.byteLength, true);
271
+ offset += 4;
272
+ out.set(key, offset);
273
+ offset += key.byteLength;
274
+
275
+ dv.setUint32(offset, value.byteLength, true);
276
+ offset += 4;
277
+ out.set(value, offset);
278
+ offset += value.byteLength;
279
+ }
280
+
281
+ return out;
282
+ }
283
+
284
+ export function schemaValidateBatch(
285
+ validator: SchemaValidator,
286
+ items: Uint8Array[],
287
+ ): Uint8Array {
288
+ return unpackBitset(validator.validateBatchPackedBitset(packBatch(items)));
289
+ }
290
+
291
+ export function schemaValidateBatchCount(
292
+ validator: SchemaValidator,
293
+ items: Uint8Array[],
294
+ ): number {
295
+ return validator.validateBatchPackedCount(packBatch(items));
296
+ }
297
+
298
+ // ── High-level string parsers (convenience) ──────────────────────
299
+ // Wrap the native packed parsers + decoders for ergonomic use, so consumers
300
+ // don't have to hand-pack buffers or decode packed pairs:
301
+ // parseQueryString("a=1&b=2") // { a: "1", b: "2" }
302
+ // parseCookieHeader("a=1; b=2") // { a: "1", b: "2" }
303
+
304
+ export function parseQueryString(
305
+ query: string,
306
+ ): Record<string, string | string[]> {
307
+ const packed = addon.queryParsePacked(encoder.encode(query));
308
+ return pairsToObject(readPairsPacked(packed));
309
+ }
310
+
311
+ export function parseCookieHeader(
312
+ header: string,
313
+ ): Record<string, string | string[]> {
314
+ const packed = addon.cookieParsePacked(encoder.encode(header));
315
+ return pairsToObject(readPairsPacked(packed));
316
+ }