@browsercore/testing 0.1.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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/bench/bench.d.ts +21 -0
  4. package/dist/bench/bench.d.ts.map +1 -0
  5. package/dist/bench/bench.js +19 -0
  6. package/dist/bench/bench.js.map +1 -0
  7. package/dist/captures/manifest.d.ts +18 -0
  8. package/dist/captures/manifest.d.ts.map +1 -0
  9. package/dist/captures/manifest.js +62 -0
  10. package/dist/captures/manifest.js.map +1 -0
  11. package/dist/categories/capture.d.ts +8 -0
  12. package/dist/categories/capture.d.ts.map +1 -0
  13. package/dist/categories/capture.js +31 -0
  14. package/dist/categories/capture.js.map +1 -0
  15. package/dist/categories/compression.d.ts +8 -0
  16. package/dist/categories/compression.d.ts.map +1 -0
  17. package/dist/categories/compression.js +15 -0
  18. package/dist/categories/compression.js.map +1 -0
  19. package/dist/categories/cookies.d.ts +8 -0
  20. package/dist/categories/cookies.d.ts.map +1 -0
  21. package/dist/categories/cookies.js +20 -0
  22. package/dist/categories/cookies.js.map +1 -0
  23. package/dist/categories/errors.d.ts +11 -0
  24. package/dist/categories/errors.d.ts.map +1 -0
  25. package/dist/categories/errors.js +41 -0
  26. package/dist/categories/errors.js.map +1 -0
  27. package/dist/categories/http.d.ts +9 -0
  28. package/dist/categories/http.d.ts.map +1 -0
  29. package/dist/categories/http.js +46 -0
  30. package/dist/categories/http.js.map +1 -0
  31. package/dist/categories/index.d.ts +17 -0
  32. package/dist/categories/index.d.ts.map +1 -0
  33. package/dist/categories/index.js +17 -0
  34. package/dist/categories/index.js.map +1 -0
  35. package/dist/categories/redirects.d.ts +8 -0
  36. package/dist/categories/redirects.d.ts.map +1 -0
  37. package/dist/categories/redirects.js +21 -0
  38. package/dist/categories/redirects.js.map +1 -0
  39. package/dist/categories/regression.d.ts +8 -0
  40. package/dist/categories/regression.d.ts.map +1 -0
  41. package/dist/categories/regression.js +28 -0
  42. package/dist/categories/regression.js.map +1 -0
  43. package/dist/categories/session.d.ts +8 -0
  44. package/dist/categories/session.d.ts.map +1 -0
  45. package/dist/categories/session.js +23 -0
  46. package/dist/categories/session.js.map +1 -0
  47. package/dist/categories/tls.d.ts +10 -0
  48. package/dist/categories/tls.d.ts.map +1 -0
  49. package/dist/categories/tls.js +48 -0
  50. package/dist/categories/tls.js.map +1 -0
  51. package/dist/categories/transport.d.ts +8 -0
  52. package/dist/categories/transport.d.ts.map +1 -0
  53. package/dist/categories/transport.js +22 -0
  54. package/dist/categories/transport.js.map +1 -0
  55. package/dist/errors.d.ts +22 -0
  56. package/dist/errors.d.ts.map +1 -0
  57. package/dist/errors.js +28 -0
  58. package/dist/errors.js.map +1 -0
  59. package/dist/fingerprint/index.d.ts +5 -0
  60. package/dist/fingerprint/index.d.ts.map +1 -0
  61. package/dist/fingerprint/index.js +4 -0
  62. package/dist/fingerprint/index.js.map +1 -0
  63. package/dist/fingerprint/ja3.d.ts +36 -0
  64. package/dist/fingerprint/ja3.d.ts.map +1 -0
  65. package/dist/fingerprint/ja3.js +141 -0
  66. package/dist/fingerprint/ja3.js.map +1 -0
  67. package/dist/fingerprint/ja4.d.ts +109 -0
  68. package/dist/fingerprint/ja4.d.ts.map +1 -0
  69. package/dist/fingerprint/ja4.js +325 -0
  70. package/dist/fingerprint/ja4.js.map +1 -0
  71. package/dist/golden/golden.d.ts +45 -0
  72. package/dist/golden/golden.d.ts.map +1 -0
  73. package/dist/golden/golden.js +216 -0
  74. package/dist/golden/golden.js.map +1 -0
  75. package/dist/index.d.ts +25 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +25 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/reference/node-reference.d.ts +83 -0
  80. package/dist/reference/node-reference.d.ts.map +1 -0
  81. package/dist/reference/node-reference.js +161 -0
  82. package/dist/reference/node-reference.js.map +1 -0
  83. package/dist/reference/reference.d.ts +143 -0
  84. package/dist/reference/reference.d.ts.map +1 -0
  85. package/dist/reference/reference.js +323 -0
  86. package/dist/reference/reference.js.map +1 -0
  87. package/dist/rfc/rfcTests.d.ts +14 -0
  88. package/dist/rfc/rfcTests.d.ts.map +1 -0
  89. package/dist/rfc/rfcTests.js +22 -0
  90. package/dist/rfc/rfcTests.js.map +1 -0
  91. package/dist/types.d.ts +140 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +34 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/utils.d.ts +29 -0
  96. package/dist/utils.d.ts.map +1 -0
  97. package/dist/utils.js +102 -0
  98. package/dist/utils.js.map +1 -0
  99. package/package.json +53 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * JA4 TLS fingerprint (docs/TEST-SUITE.md, Category 4).
3
+ *
4
+ * JA4 is a multi-part fingerprint family that captures significantly more signal
5
+ * than JA3. We compute the standard four-part TLS fingerprint:
6
+ *
7
+ * JA4_a connection prefix — transport, TLS version, SNI flag, cipher count,
8
+ * extension count, ALPN code.
9
+ * JA4_b cipher-suite hash — sorted non-GREASE cipher suites, SHA-256,
10
+ * first 12 hex chars.
11
+ * JA4_c extension hash — sorted non-GREASE extensions (excluding SNI +
12
+ * ALPN), SHA-256, first 12 hex chars.
13
+ * JA4_f raw-fields hash — TLS version, ciphers, extensions, supported
14
+ * groups, EC point formats, SHA-256, first 12 hex.
15
+ *
16
+ * The concatenated `JA4_a_JA4_b_JA4_c_JA4_f` string is the canonical JA4 tag.
17
+ *
18
+ * JA4H (the HTTP-layer sibling) is computed separately by {@link computeJa4h}
19
+ * from method/version/headers/cookies — it is the HTTP analogue used by the
20
+ * reference provider to fingerprint a captured request.
21
+ *
22
+ * Reference: https://github.com/FoxIO-LLC/JA4
23
+ */
24
+ import { createHash } from "node:crypto";
25
+ /** Reasons a ClientHello cannot be parsed into a JA4 input. */
26
+ export class Ja4ParseError extends Error {
27
+ kind = "Ja4ParseError";
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "Ja4ParseError";
31
+ }
32
+ }
33
+ /** GREASE values per RFC 8701 — these are reserved and must be ignored. */
34
+ const GREASE_VALUES = new Set([
35
+ 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a,
36
+ 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa,
37
+ ]);
38
+ /** TLS extension type numbers we special-case when parsing. */
39
+ const EXT_SNI = 0x0000;
40
+ const EXT_SUPPORTED_GROUPS = 0x000a;
41
+ const EXT_EC_POINT_FORMATS = 0x000b;
42
+ const EXT_ALPN = 0x0010;
43
+ const EXT_SUPPORTED_VERSIONS = 0x002b;
44
+ /** TLS protocol-version codes → human-readable version string. */
45
+ const TLS_VERSIONS = new Map([
46
+ [0x0304, "13"],
47
+ [0x0303, "12"],
48
+ [0x0302, "11"],
49
+ [0x0301, "10"],
50
+ [0x0300, "09"],
51
+ ]);
52
+ /** Read a big-endian uint16 at `offset` in `buf` (unchecked — caller bounds). */
53
+ function uint16(buf, offset) {
54
+ return (buf[offset] << 8) | buf[offset + 1];
55
+ }
56
+ /** Read a 24-bit big-endian integer at `offset` in `buf`. */
57
+ function uint24(buf, offset) {
58
+ return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
59
+ }
60
+ /** Format a number as a 4-char lowercase hex string (`0x1a2b` → `"1a2b"`). */
61
+ function hex4(value) {
62
+ return value.toString(16).padStart(4, "0");
63
+ }
64
+ /** First 12 hex chars of the SHA-256 digest of `input`. */
65
+ function sha256First12(input) {
66
+ return createHash("sha256").update(input).digest("hex").slice(0, 12);
67
+ }
68
+ /**
69
+ * Parse a TLS ClientHello (either wrapped in a TLS record or bare) into the
70
+ * fields JA4 needs. Throws {@link Ja4ParseError} on malformed input.
71
+ */
72
+ export function parseJa4ClientHello(clientHello) {
73
+ let pos;
74
+ let handshakeLen;
75
+ if (clientHello.length === 0) {
76
+ throw new Ja4ParseError("ClientHello is empty");
77
+ }
78
+ // TLS record wrapper: ContentType handshake (0x16) + version(2) + length(2).
79
+ if (clientHello[0] === 0x16) {
80
+ if (clientHello.length < 5) {
81
+ throw new Ja4ParseError("TLS record too short");
82
+ }
83
+ pos = 5;
84
+ if (clientHello[pos] !== 0x01) {
85
+ throw new Ja4ParseError(`Expected ClientHello (0x01) at record+0, got 0x${clientHello[pos]?.toString(16)}`);
86
+ }
87
+ handshakeLen = uint24(clientHello, pos + 1);
88
+ pos += 4;
89
+ const available = clientHello.length - pos;
90
+ if (handshakeLen > available) {
91
+ throw new Ja4ParseError(`Handshake length ${handshakeLen} exceeds available ${available} bytes`);
92
+ }
93
+ }
94
+ else if (clientHello[0] === 0x01) {
95
+ // Bare handshake.
96
+ if (clientHello.length < 4) {
97
+ throw new Ja4ParseError("Bare ClientHello too short");
98
+ }
99
+ handshakeLen = uint24(clientHello, 1);
100
+ pos = 4;
101
+ const available = clientHello.length - pos;
102
+ if (handshakeLen > available) {
103
+ throw new Ja4ParseError(`Handshake length ${handshakeLen} exceeds available ${available} bytes`);
104
+ }
105
+ }
106
+ else {
107
+ throw new Ja4ParseError(`Not a TLS record or ClientHello (first byte 0x${clientHello[0]?.toString(16)})`);
108
+ }
109
+ const end = pos + handshakeLen;
110
+ if (pos + 2 > end) {
111
+ throw new Ja4ParseError("ClientHello too short for version");
112
+ }
113
+ // client_version(2)
114
+ const versionCode = uint16(clientHello, pos);
115
+ pos += 2;
116
+ // random(32)
117
+ pos += 32;
118
+ if (pos > end) {
119
+ throw new Ja4ParseError("ClientHello truncated before session id");
120
+ }
121
+ // session_id(variable)
122
+ const sessionIdLen = clientHello[pos];
123
+ pos += 1 + sessionIdLen;
124
+ if (pos + 2 > end) {
125
+ throw new Ja4ParseError("ClientHello truncated before cipher suites");
126
+ }
127
+ // cipher_suites(variable)
128
+ const cipherSuitesLen = uint16(clientHello, pos);
129
+ pos += 2;
130
+ if (pos + cipherSuitesLen > end) {
131
+ throw new Ja4ParseError("ClientHello truncated in cipher suites");
132
+ }
133
+ const cipherSuites = [];
134
+ for (let i = 0; i + 1 < cipherSuitesLen; i += 2) {
135
+ const suite = uint16(clientHello, pos + i);
136
+ if (!GREASE_VALUES.has(suite)) {
137
+ cipherSuites.push(suite);
138
+ }
139
+ }
140
+ pos += cipherSuitesLen;
141
+ if (pos + 1 > end) {
142
+ throw new Ja4ParseError("ClientHello truncated before compression methods");
143
+ }
144
+ // compression_methods(variable)
145
+ const compLen = clientHello[pos];
146
+ pos += 1 + compLen;
147
+ if (pos + 2 > end) {
148
+ // No extensions present.
149
+ return {
150
+ tlsVersion: TLS_VERSIONS.get(versionCode) ?? versionCode.toString(16).padStart(2, "0"),
151
+ sniPresent: false,
152
+ cipherSuites,
153
+ extensions: [],
154
+ supportedGroups: [],
155
+ ecPointFormats: [],
156
+ alpnRaw: "",
157
+ };
158
+ }
159
+ // extensions(variable)
160
+ const extensionsLen = uint16(clientHello, pos);
161
+ pos += 2;
162
+ const extensionsEnd = pos + extensionsLen;
163
+ const extensions = [];
164
+ let sniPresent = false;
165
+ const supportedGroups = [];
166
+ const ecPointFormats = [];
167
+ let alpnRaw = "";
168
+ while (pos + 4 <= extensionsEnd) {
169
+ const extType = uint16(clientHello, pos);
170
+ const extLen = uint16(clientHello, pos + 2);
171
+ pos += 4;
172
+ if (!GREASE_VALUES.has(extType)) {
173
+ extensions.push(extType);
174
+ }
175
+ switch (extType) {
176
+ case EXT_SNI:
177
+ sniPresent = true;
178
+ break;
179
+ case EXT_SUPPORTED_GROUPS:
180
+ if (extLen >= 4) {
181
+ const listLen = uint16(clientHello, pos);
182
+ for (let i = 0; i + 1 < listLen; i += 2) {
183
+ const group = uint16(clientHello, pos + 2 + i);
184
+ if (!GREASE_VALUES.has(group)) {
185
+ supportedGroups.push(group);
186
+ }
187
+ }
188
+ }
189
+ break;
190
+ case EXT_EC_POINT_FORMATS:
191
+ if (extLen >= 1) {
192
+ const listLen = clientHello[pos];
193
+ for (let i = 0; i < listLen; i++) {
194
+ ecPointFormats.push(clientHello[pos + 1 + i]);
195
+ }
196
+ }
197
+ break;
198
+ case EXT_ALPN:
199
+ if (extLen >= 2) {
200
+ const listLen = uint16(clientHello, pos);
201
+ let cursor = pos + 2;
202
+ const protocols = [];
203
+ const listEnd = cursor + listLen;
204
+ while (cursor < listEnd) {
205
+ const protoLen = clientHello[cursor];
206
+ cursor += 1;
207
+ protocols.push(new TextDecoder().decode(clientHello.subarray(cursor, cursor + protoLen)));
208
+ cursor += protoLen;
209
+ }
210
+ alpnRaw = protocols.join(",");
211
+ }
212
+ break;
213
+ case EXT_SUPPORTED_VERSIONS:
214
+ // JA4 uses the highest supported version from this extension.
215
+ break;
216
+ default:
217
+ break;
218
+ }
219
+ pos += extLen;
220
+ }
221
+ return {
222
+ tlsVersion: TLS_VERSIONS.get(versionCode) ?? versionCode.toString(16).padStart(2, "0"),
223
+ sniPresent,
224
+ cipherSuites,
225
+ extensions,
226
+ supportedGroups,
227
+ ecPointFormats,
228
+ alpnRaw,
229
+ };
230
+ }
231
+ /** ALPN code: first char of first ALPN + first char of last ALPN, or "00". */
232
+ function alpnCode(alpnRaw) {
233
+ if (alpnRaw.length === 0) {
234
+ return "00";
235
+ }
236
+ const protocols = alpnRaw.split(",");
237
+ const first = protocols[0] ?? "";
238
+ const last = protocols[protocols.length - 1] ?? "";
239
+ const a = first.length > 0 ? first[0] : "0";
240
+ const b = last.length > 0 ? last[0] : "0";
241
+ return `${a}${b}`.toLowerCase();
242
+ }
243
+ /**
244
+ * Compute the canonical JA4 tag (`JA4_a_JA4_b_JA4_c_JA4_f`) from a ClientHello.
245
+ *
246
+ * This is the string form used by the public API (index.ts) and the reference
247
+ * provider. Use {@link computeJa4Fingerprint} when you need the parts.
248
+ */
249
+ export function computeJa4(clientHello) {
250
+ return computeJa4Fingerprint(clientHello).tag;
251
+ }
252
+ /**
253
+ * Compute the four-part JA4 TLS fingerprint from a ClientHello buffer.
254
+ *
255
+ * Returns the individual parts plus the canonical `JA4_a_JA4_b_JA4_c_JA4_f`
256
+ * tag. The caller can use any part — comparison is typically done against the
257
+ * full tag or against JA4_a (the connection prefix) for grouping.
258
+ */
259
+ export function computeJa4Fingerprint(clientHello) {
260
+ const hello = parseJa4ClientHello(clientHello);
261
+ // JA4_a: t{ciphers:02d}{exts:02d}{sni_flag}{version}{alpn}
262
+ const sniFlag = hello.sniPresent ? "d" : "i";
263
+ const a = `t${hello.cipherSuites.length.toString().padStart(2, "0")}${hello.extensions.length
264
+ .toString()
265
+ .padStart(2, "0")}${sniFlag}${hello.tlsVersion}${alpnCode(hello.alpnRaw)}`;
266
+ // JA4_b: sorted cipher suites, 4-char hex, SHA-256, first 12 hex.
267
+ const sortedCiphers = [...hello.cipherSuites].sort((x, y) => x - y).map(hex4).join("");
268
+ const b = sortedCiphers.length > 0 ? sha256First12(sortedCiphers) : "000000000000";
269
+ // JA4_c: sorted extensions (excluding SNI=0, ALPN=16), 4-char hex.
270
+ const filteredExts = hello.extensions
271
+ .filter((e) => e !== EXT_SNI && e !== EXT_ALPN)
272
+ .sort((x, y) => x - y)
273
+ .map(hex4)
274
+ .join("");
275
+ const c = filteredExts.length > 0 ? sha256First12(filteredExts) : "000000000000";
276
+ // JA4_f: raw fields — version, ciphers, extensions, supported groups, ec.
277
+ const raw = [
278
+ hex4(clientHello.length > 1 ? uint16(clientHello, 0) : 0),
279
+ hello.cipherSuites.map(hex4).join(""),
280
+ hello.extensions.map(hex4).join(""),
281
+ hello.supportedGroups.map(hex4).join(""),
282
+ hello.ecPointFormats.map(hex4).join(""),
283
+ ].join("");
284
+ const f = sha256First12(raw);
285
+ return { a, b, c, f, tag: `${a}_${b}_${c}_${f}` };
286
+ }
287
+ /**
288
+ * Compute the four-part JA4H HTTP fingerprint from a captured request.
289
+ *
290
+ * Format: `JA4H_a_JA4H_b_JA4H_c_JA4H_d` where:
291
+ * - JA4H_a: `{method:02s}{version}{has_cookies:d}{has_referer:d}{header_count:02d}{lang_prefix}`
292
+ * - JA4H_b: sorted cookie name=value pairs, SHA-256, first 12 hex.
293
+ * - JA4H_c: sorted cookie names, SHA-256, first 12 hex.
294
+ * - JA4H_d: sorted header names (excluding cookie), SHA-256, first 12 hex.
295
+ */
296
+ export function computeJa4h(request) {
297
+ const methodCode = request.method.slice(0, 2).toLowerCase();
298
+ const versionCode = request.httpVersion === "1.1"
299
+ ? "11"
300
+ : request.httpVersion === "2"
301
+ ? "02"
302
+ : request.httpVersion === "3"
303
+ ? "03"
304
+ : "00";
305
+ const hasCookies = request.cookies.length > 0 ? "c" : "n";
306
+ const hasReferer = request.headerNames.some((h) => h === "referer") ? "r" : "n";
307
+ const headerCount = request.headerNames.length.toString().padStart(2, "0");
308
+ const langPrefix = (request.acceptLanguage ?? "").slice(0, 4).padEnd(4, "0").toLowerCase();
309
+ const a = `${methodCode}${versionCode}${hasCookies}${hasReferer}${headerCount}${langPrefix}`;
310
+ const sortedCookies = [...request.cookies].sort().join(",");
311
+ const b = sortedCookies.length > 0 ? sha256First12(sortedCookies) : "000000000000";
312
+ const sortedCookieNames = [...request.cookies]
313
+ .map((c) => c.split("=")[0] ?? "")
314
+ .filter((n) => n.length > 0)
315
+ .sort()
316
+ .join(",");
317
+ const c = sortedCookieNames.length > 0 ? sha256First12(sortedCookieNames) : "000000000000";
318
+ const sortedHeaders = [...request.headerNames]
319
+ .filter((h) => h !== "cookie")
320
+ .sort()
321
+ .join(",");
322
+ const d = sortedHeaders.length > 0 ? sha256First12(sortedHeaders) : "000000000000";
323
+ return { a, b, c, d, tag: `${a}_${b}_${c}_${d}` };
324
+ }
325
+ //# sourceMappingURL=ja4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ja4.js","sourceRoot":"","sources":["../../src/fingerprint/ja4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,+DAA+D;AAC/D,MAAM,OAAO,aAAc,SAAQ,KAAK;IACpB,IAAI,GAAG,eAAwB,CAAC;IAChD,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAChC,CAAC;CACJ;AAuCD,2EAA2E;AAC3E,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC;IAC/C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9D,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CACjE,CAAC,CAAC;AAEH,+DAA+D;AAC/D,MAAM,OAAO,GAAG,MAAM,CAAC;AACvB,MAAM,oBAAoB,GAAG,MAAM,CAAC;AACpC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AACpC,MAAM,QAAQ,GAAG,MAAM,CAAC;AACxB,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAEtC,kEAAkE;AAClE,MAAM,YAAY,GAAG,IAAI,GAAG,CAAiB;IACzC,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,IAAI,CAAC;CACjB,CAAC,CAAC;AAEH,iFAAiF;AACjF,SAAS,MAAM,CAAC,GAAe,EAAE,MAAc;IAC3C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;AAClD,CAAC;AAED,6DAA6D;AAC7D,SAAS,MAAM,CAAC,GAAe,EAAE,MAAc;IAC3C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;AAC7E,CAAC;AAED,8EAA8E;AAC9E,SAAS,IAAI,CAAC,KAAa;IACvB,OAAO,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,2DAA2D;AAC3D,SAAS,aAAa,CAAC,KAAa;IAChC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAuB;IACvD,IAAI,GAAW,CAAC;IAChB,IAAI,YAAoB,CAAC;IAEzB,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,aAAa,CAAC,sBAAsB,CAAC,CAAC;IACpD,CAAC;IAED,6EAA6E;IAC7E,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1B,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,aAAa,CAAC,sBAAsB,CAAC,CAAC;QACpD,CAAC;QACD,GAAG,GAAG,CAAC,CAAC;QACR,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CACnB,kDAAkD,WAAW,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CACrF,CAAC;QACN,CAAC;QACD,YAAY,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAC5C,GAAG,IAAI,CAAC,CAAC;QACT,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,GAAG,CAAC;QAC3C,IAAI,YAAY,GAAG,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,aAAa,CACnB,oBAAoB,YAAY,sBAAsB,SAAS,QAAQ,CAC1E,CAAC;QACN,CAAC;IACL,CAAC;SAAM,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACjC,kBAAkB;QAClB,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,aAAa,CAAC,4BAA4B,CAAC,CAAC;QAC1D,CAAC;QACD,YAAY,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACtC,GAAG,GAAG,CAAC,CAAC;QACR,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,GAAG,CAAC;QAC3C,IAAI,YAAY,GAAG,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,aAAa,CACnB,oBAAoB,YAAY,sBAAsB,SAAS,QAAQ,CAC1E,CAAC;QACN,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,MAAM,IAAI,aAAa,CACnB,iDAAiD,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,CACnF,CAAC;IACN,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,GAAG,YAAY,CAAC;IAC/B,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,MAAM,IAAI,aAAa,CAAC,mCAAmC,CAAC,CAAC;IACjE,CAAC;IAED,oBAAoB;IACpB,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAC7C,GAAG,IAAI,CAAC,CAAC;IACT,aAAa;IACb,GAAG,IAAI,EAAE,CAAC;IACV,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;QACZ,MAAM,IAAI,aAAa,CAAC,yCAAyC,CAAC,CAAC;IACvE,CAAC;IACD,uBAAuB;IACvB,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,CAAE,CAAC;IACvC,GAAG,IAAI,CAAC,GAAG,YAAY,CAAC;IACxB,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,MAAM,IAAI,aAAa,CAAC,4CAA4C,CAAC,CAAC;IAC1E,CAAC;IACD,0BAA0B;IAC1B,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACjD,GAAG,IAAI,CAAC,CAAC;IACT,IAAI,GAAG,GAAG,eAAe,GAAG,GAAG,EAAE,CAAC;QAC9B,MAAM,IAAI,aAAa,CAAC,wCAAwC,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,eAAe,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;IACL,CAAC;IACD,GAAG,IAAI,eAAe,CAAC;IACvB,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,MAAM,IAAI,aAAa,CAAC,kDAAkD,CAAC,CAAC;IAChF,CAAC;IACD,gCAAgC;IAChC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAE,CAAC;IAClC,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC;IACnB,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,yBAAyB;QACzB,OAAO;YACH,UAAU,EAAE,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;YACtF,UAAU,EAAE,KAAK;YACjB,YAAY;YACZ,UAAU,EAAE,EAAE;YACd,eAAe,EAAE,EAAE;YACnB,cAAc,EAAE,EAAE;YAClB,OAAO,EAAE,EAAE;SACd,CAAC;IACN,CAAC;IACD,uBAAuB;IACvB,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAC/C,GAAG,IAAI,CAAC,CAAC;IACT,MAAM,aAAa,GAAG,GAAG,GAAG,aAAa,CAAC;IAE1C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,OAAO,GAAG,GAAG,CAAC,IAAI,aAAa,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAC5C,GAAG,IAAI,CAAC,CAAC;QAET,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;QAED,QAAQ,OAAO,EAAE,CAAC;YACd,KAAK,OAAO;gBACR,UAAU,GAAG,IAAI,CAAC;gBAClB,MAAM;YACV,KAAK,oBAAoB;gBACrB,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;oBACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;wBACtC,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;wBAC/C,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;4BAC5B,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;wBAChC,CAAC;oBACL,CAAC;gBACL,CAAC;gBACD,MAAM;YACV,KAAK,oBAAoB;gBACrB,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAE,CAAC;oBAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC/B,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;oBACnD,CAAC;gBACL,CAAC;gBACD,MAAM;YACV,KAAK,QAAQ;gBACT,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;oBACzC,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC;oBACrB,MAAM,SAAS,GAAa,EAAE,CAAC;oBAC/B,MAAM,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;oBACjC,OAAO,MAAM,GAAG,OAAO,EAAE,CAAC;wBACtB,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAE,CAAC;wBACtC,MAAM,IAAI,CAAC,CAAC;wBACZ,SAAS,CAAC,IAAI,CACV,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAC5E,CAAC;wBACF,MAAM,IAAI,QAAQ,CAAC;oBACvB,CAAC;oBACD,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAClC,CAAC;gBACD,MAAM;YACV,KAAK,sBAAsB;gBACvB,8DAA8D;gBAC9D,MAAM;YACV;gBACI,MAAM;QACd,CAAC;QAED,GAAG,IAAI,MAAM,CAAC;IAClB,CAAC;IAED,OAAO;QACH,UAAU,EAAE,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;QACtF,UAAU;QACV,YAAY;QACZ,UAAU;QACV,eAAe;QACf,cAAc;QACd,OAAO;KACV,CAAC;AACN,CAAC;AAED,8EAA8E;AAC9E,SAAS,QAAQ,CAAC,OAAe;IAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3C,OAAO,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,WAAuB;IAC9C,OAAO,qBAAqB,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,WAAuB;IACzD,MAAM,KAAK,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAE/C,2DAA2D;IAC3D,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7C,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM;SACxF,QAAQ,EAAE;SACV,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,OAAO,GAAG,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;IAE/E,kEAAkE;IAClE,MAAM,aAAa,GAAG,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvF,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAEnF,mEAAmE;IACnE,MAAM,YAAY,GAAG,KAAK,CAAC,UAAU;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,QAAQ,CAAC;SAC9C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;SACrB,GAAG,CAAC,IAAI,CAAC;SACT,IAAI,CAAC,EAAE,CAAC,CAAC;IACd,MAAM,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAEjF,0EAA0E;IAC1E,MAAM,GAAG,GAAG;QACR,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;KAC1C,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,MAAM,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAE7B,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AACtD,CAAC;AAmBD;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,OAAoB;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,MAAM,WAAW,GACb,OAAO,CAAC,WAAW,KAAK,KAAK;QACzB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,OAAO,CAAC,WAAW,KAAK,GAAG;YAC3B,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,OAAO,CAAC,WAAW,KAAK,GAAG;gBAC3B,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI,CAAC;IACnB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAChF,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3E,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAE3F,MAAM,CAAC,GAAG,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,EAAE,CAAC;IAE7F,MAAM,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAEnF,MAAM,iBAAiB,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;SACzC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACjC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,MAAM,CAAC,GAAG,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAE3F,MAAM,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;SACzC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC;SAC7B,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAEnF,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Golden packet capture loading + comparison.
3
+ *
4
+ * Captures live on disk as a `.bin` (raw wire bytes) plus a `.meta.json`
5
+ * sidecar describing the capture and listing the byte ranges that are
6
+ * intentionally randomized by the protocol spec (ephemeral keys, nonces,
7
+ * GREASE, client_random). Those ranges MUST be masked before comparison —
8
+ * that is the core mechanism behind Category 14 (packet capture comparison).
9
+ *
10
+ * CaptureId format: a path-like triple `${profile}/${protocol}/${record}`
11
+ * (e.g. `chrome-140/tls/client_hello`) that maps directly onto the
12
+ * `captures/<profile>/<protocol>/<record>.{bin,meta.json}` layout.
13
+ */
14
+ import type { CaptureId, CaptureMeta, ComparisonResult, ComparisonResultWithIgnore, GoldenCapture } from "../types.js";
15
+ /**
16
+ * Load a golden capture by id.
17
+ *
18
+ * Reads `<captureId>.bin` and its sibling `.meta.json` from the captures
19
+ * directory, validates the sidecar, and returns a typed {@link GoldenCapture}.
20
+ * Throws {@link TestingError} when the id is malformed, the files are
21
+ * missing, or the sidecar fails validation.
22
+ */
23
+ export declare function loadGolden(captureId: CaptureId): GoldenCapture;
24
+ /** Load the {@link CaptureMeta} sidecar for a capture. */
25
+ export declare function loadCaptureMeta(captureId: CaptureId): CaptureMeta;
26
+ /**
27
+ * Compare actual bytes against a golden capture. Real byte comparison —
28
+ * throws {@link GoldenMismatchError} when they diverge.
29
+ *
30
+ * Uses a strict byte comparison (no randomized-field masking). For the
31
+ * tolerant comparison used by Category 14, see
32
+ * {@link compareAgainstGoldenWithIgnore}.
33
+ */
34
+ export declare function compareAgainstGolden(actual: Uint8Array, captureId: CaptureId): ComparisonResult;
35
+ /**
36
+ * Compare actual bytes against a golden capture while masking the byte ranges
37
+ * the protocol intentionally randomizes (ephemeral keys, nonces, GREASE,
38
+ * client_random). This is the Category 14 tolerant comparison.
39
+ *
40
+ * Returns a {@link ComparisonResultWithIgnore} that reports which ranges were
41
+ * masked. Throws {@link GoldenMismatchError} only when bytes OUTSIDE the
42
+ * masked ranges diverge.
43
+ */
44
+ export declare function compareAgainstGoldenWithIgnore(actual: Uint8Array, captureId: CaptureId): ComparisonResultWithIgnore;
45
+ //# sourceMappingURL=golden.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"golden.d.ts","sourceRoot":"","sources":["../../src/golden/golden.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EACR,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,0BAA0B,EAC1B,aAAa,EAEhB,MAAM,aAAa,CAAC;AA6HrB;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,GAAG,aAAa,CA2B9D;AAYD,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,WAAW,CAUjE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,GAAG,gBAAgB,CAa/F;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAC1C,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,SAAS,GACrB,0BAA0B,CAe5B"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Golden packet capture loading + comparison.
3
+ *
4
+ * Captures live on disk as a `.bin` (raw wire bytes) plus a `.meta.json`
5
+ * sidecar describing the capture and listing the byte ranges that are
6
+ * intentionally randomized by the protocol spec (ephemeral keys, nonces,
7
+ * GREASE, client_random). Those ranges MUST be masked before comparison —
8
+ * that is the core mechanism behind Category 14 (packet capture comparison).
9
+ *
10
+ * CaptureId format: a path-like triple `${profile}/${protocol}/${record}`
11
+ * (e.g. `chrome-140/tls/client_hello`) that maps directly onto the
12
+ * `captures/<profile>/<protocol>/<record>.{bin,meta.json}` layout.
13
+ */
14
+ import { readFileSync } from "node:fs";
15
+ import { fileURLToPath } from "node:url";
16
+ import { dirname, join } from "node:path";
17
+ import { compareBytes, compareBytesWithIgnore } from "../utils.js";
18
+ import { GoldenMismatchError, TestingError } from "../errors.js";
19
+ const here = dirname(fileURLToPath(import.meta.url));
20
+ // src/golden -> package root -> captures/
21
+ const capturesDir = join(here, "..", "..", "captures");
22
+ /** Parse a CaptureId of the form `${profile}/${protocol}/${record}`. */
23
+ function parseCaptureId(captureId) {
24
+ const EXPECTED_FORMAT = "${profile}/${protocol}/${record}";
25
+ const parts = captureId.split("/");
26
+ if (parts.length !== 3) {
27
+ throw new TestingError(`Malformed CaptureId "${captureId}" — expected ${EXPECTED_FORMAT}`);
28
+ }
29
+ const [profile, protocol, record] = parts;
30
+ if (profile === undefined || protocol === undefined || record === undefined) {
31
+ throw new TestingError(`Malformed CaptureId "${captureId}" — expected ${EXPECTED_FORMAT}`);
32
+ }
33
+ return { profile, protocol, record };
34
+ }
35
+ /** Validate unknown JSON data as a {@link CaptureMeta} (no zod dep here). */
36
+ function parseCaptureMeta(raw, captureId) {
37
+ if (typeof raw !== "object" || raw === null) {
38
+ throw new TestingError(`Capture meta for ${captureId} is not an object`);
39
+ }
40
+ const obj = raw;
41
+ const source = obj.source;
42
+ if (source !== "curl-impersonate" && source !== "real-browser") {
43
+ throw new TestingError(`Capture meta for ${captureId} has invalid source: ${String(source)}`);
44
+ }
45
+ const protocol = obj.protocol;
46
+ if (protocol !== "tls" && protocol !== "http2" && protocol !== "http1" && protocol !== "tcp") {
47
+ throw new TestingError(`Capture meta for ${captureId} has invalid protocol: ${String(protocol)}`);
48
+ }
49
+ const record = obj.record;
50
+ if (record !== "client_hello" &&
51
+ record !== "settings" &&
52
+ record !== "headers" &&
53
+ record !== "server_hello") {
54
+ throw new TestingError(`Capture meta for ${captureId} has invalid record: ${String(record)}`);
55
+ }
56
+ const description = obj.description;
57
+ if (typeof description !== "string") {
58
+ throw new TestingError(`Capture meta for ${captureId} has non-string description`);
59
+ }
60
+ const createdAt = obj.createdAt;
61
+ if (typeof createdAt !== "string") {
62
+ throw new TestingError(`Capture meta for ${captureId} has non-string createdAt`);
63
+ }
64
+ const randomizedFields = parseRandomizedFields(obj.randomizedFields, captureId);
65
+ return {
66
+ source,
67
+ profile: obj.profile,
68
+ protocol,
69
+ record,
70
+ description,
71
+ randomizedFields,
72
+ createdAt,
73
+ };
74
+ }
75
+ /** Validate unknown data as a list of {@link RandomizedField}. */
76
+ function parseRandomizedFields(raw, captureId) {
77
+ if (!Array.isArray(raw)) {
78
+ throw new TestingError(`Capture meta for ${captureId} has non-array randomizedFields`);
79
+ }
80
+ return raw.map((item, i) => {
81
+ if (typeof item !== "object" || item === null) {
82
+ throw new TestingError(`Capture meta for ${captureId} randomizedFields[${i}] is not an object`);
83
+ }
84
+ const obj = item;
85
+ const byteOffset = obj.byteOffset;
86
+ if (typeof byteOffset !== "number" || byteOffset < 0) {
87
+ throw new TestingError(`Capture meta for ${captureId} randomizedFields[${i}].byteOffset invalid`);
88
+ }
89
+ const length = obj.length;
90
+ if (typeof length !== "number" || length < 0) {
91
+ throw new TestingError(`Capture meta for ${captureId} randomizedFields[${i}].length invalid`);
92
+ }
93
+ const reason = obj.reason;
94
+ if (reason !== "ephemeral_key" && reason !== "nonce" && reason !== "grease" && reason !== "random") {
95
+ throw new TestingError(`Capture meta for ${captureId} randomizedFields[${i}].reason invalid: ${String(reason)}`);
96
+ }
97
+ return { byteOffset, length, reason };
98
+ });
99
+ }
100
+ /** Resolve a CaptureId to its on-disk `.bin` + `.meta.json` paths. */
101
+ function resolveCapturePaths(captureId) {
102
+ const { profile, protocol, record } = parseCaptureId(captureId);
103
+ const base = join(capturesDir, profile, protocol, record);
104
+ return { binPath: `${base}.bin`, metaPath: `${base}.meta.json` };
105
+ }
106
+ /**
107
+ * Load a golden capture by id.
108
+ *
109
+ * Reads `<captureId>.bin` and its sibling `.meta.json` from the captures
110
+ * directory, validates the sidecar, and returns a typed {@link GoldenCapture}.
111
+ * Throws {@link TestingError} when the id is malformed, the files are
112
+ * missing, or the sidecar fails validation.
113
+ */
114
+ export function loadGolden(captureId) {
115
+ const { binPath, metaPath } = resolveCapturePaths(captureId);
116
+ let bytes;
117
+ try {
118
+ bytes = readFileSync(binPath);
119
+ }
120
+ catch (e) {
121
+ const cause = e instanceof Error ? e : new Error(String(e));
122
+ throw new TestingError(`Failed to read capture bytes at ${binPath}`, { cause });
123
+ }
124
+ let meta;
125
+ try {
126
+ meta = JSON.parse(readFileSync(metaPath, "utf8"));
127
+ }
128
+ catch (e) {
129
+ const cause = e instanceof Error ? e : new Error(String(e));
130
+ throw new TestingError(`Failed to read/parse capture meta at ${metaPath}`, { cause });
131
+ }
132
+ const parsed = parseCaptureMeta(meta, captureId);
133
+ return {
134
+ id: captureId,
135
+ source: parsed.source === "curl-impersonate" ? parseSource(parsed.profile) : "chrome-140",
136
+ protocol: parsed.protocol,
137
+ bytes,
138
+ description: parsed.description,
139
+ };
140
+ }
141
+ /** Derive a {@link CaptureSource} from a profile id (best-effort). */
142
+ function parseSource(profile) {
143
+ const p = String(profile);
144
+ if (p.startsWith("chrome"))
145
+ return "chrome-140";
146
+ if (p.startsWith("firefox"))
147
+ return "firefox-135";
148
+ if (p.startsWith("safari"))
149
+ return "safari-18";
150
+ if (p.startsWith("edge"))
151
+ return "edge-140";
152
+ return "chrome-140";
153
+ }
154
+ /** Load the {@link CaptureMeta} sidecar for a capture. */
155
+ export function loadCaptureMeta(captureId) {
156
+ const { metaPath } = resolveCapturePaths(captureId);
157
+ let raw;
158
+ try {
159
+ raw = JSON.parse(readFileSync(metaPath, "utf8"));
160
+ }
161
+ catch (e) {
162
+ const cause = e instanceof Error ? e : new Error(String(e));
163
+ throw new TestingError(`Failed to read/parse capture meta at ${metaPath}`, { cause });
164
+ }
165
+ return parseCaptureMeta(raw, captureId);
166
+ }
167
+ /**
168
+ * Compare actual bytes against a golden capture. Real byte comparison —
169
+ * throws {@link GoldenMismatchError} when they diverge.
170
+ *
171
+ * Uses a strict byte comparison (no randomized-field masking). For the
172
+ * tolerant comparison used by Category 14, see
173
+ * {@link compareAgainstGoldenWithIgnore}.
174
+ */
175
+ export function compareAgainstGolden(actual, captureId) {
176
+ let expected;
177
+ try {
178
+ expected = loadGolden(captureId).bytes;
179
+ }
180
+ catch (e) {
181
+ const cause = e instanceof Error ? e : new Error(String(e));
182
+ throw new TestingError(`Failed to load golden ${captureId}`, { cause });
183
+ }
184
+ const result = compareBytes(actual, expected);
185
+ if (!result.matches) {
186
+ throw new GoldenMismatchError(captureId, result.divergenceByteIndex ?? actual.length);
187
+ }
188
+ return result;
189
+ }
190
+ /**
191
+ * Compare actual bytes against a golden capture while masking the byte ranges
192
+ * the protocol intentionally randomizes (ephemeral keys, nonces, GREASE,
193
+ * client_random). This is the Category 14 tolerant comparison.
194
+ *
195
+ * Returns a {@link ComparisonResultWithIgnore} that reports which ranges were
196
+ * masked. Throws {@link GoldenMismatchError} only when bytes OUTSIDE the
197
+ * masked ranges diverge.
198
+ */
199
+ export function compareAgainstGoldenWithIgnore(actual, captureId) {
200
+ let expected;
201
+ let meta;
202
+ try {
203
+ expected = loadGolden(captureId).bytes;
204
+ meta = loadCaptureMeta(captureId);
205
+ }
206
+ catch (e) {
207
+ const cause = e instanceof Error ? e : new Error(String(e));
208
+ throw new TestingError(`Failed to load golden ${captureId}`, { cause });
209
+ }
210
+ const result = compareBytesWithIgnore(actual, expected, meta.randomizedFields);
211
+ if (!result.matches) {
212
+ throw new GoldenMismatchError(captureId, result.divergenceByteIndex ?? actual.length);
213
+ }
214
+ return result;
215
+ }
216
+ //# sourceMappingURL=golden.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"golden.js","sourceRoot":"","sources":["../../src/golden/golden.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @browsercore/testing — public API surface.
3
+ *
4
+ * Protocol verification: RFC compliance tests, browser golden packet captures,
5
+ * integration tests, and benchmarking.
6
+ */
7
+ export { loadGolden, compareAgainstGolden } from "./golden/golden.js";
8
+ export { runTlsCompliance, runHttp2Compliance, runHttp1Compliance } from "./rfc/rfcTests.js";
9
+ export { benchmarkTlsHandshake, benchmarkHttp2Request } from "./bench/bench.js";
10
+ export { GoldenMismatchError, TestingError } from "./errors.js";
11
+ export type { BenchStats, CaptureId, CaptureProtocol, CaptureSource, ComparisonResult, GoldenCapture, TestCase, TestCaseId, TestResult, } from "./types.js";
12
+ export { assertNever, bytesToHex, compareBytes, createId } from "./utils.js";
13
+ export { TestCategory } from "./types.js";
14
+ export type { SpecTestCase, TestCategoryId, TestRun, TestStatus } from "./types.js";
15
+ export * as categories from "./categories/index.js";
16
+ export { compareBytesWithIgnore } from "./utils.js";
17
+ export type { CaptureMeta, ComparisonResultWithIgnore, RandomizedField } from "./types.js";
18
+ export { captures, type CaptureEntry } from "./captures/manifest.js";
19
+ export { createReferenceProvider, CurlImpersonateProvider, RealBrowserCaptureProvider, } from "./reference/reference.js";
20
+ export type { CurlImpersonateOptions, Fingerprint, RealBrowserOptions, ReferenceProvider, ReferenceProviderKind, } from "./reference/reference.js";
21
+ export { computeJa3, computeJa4, Ja3ParseError, parseClientHello } from "./fingerprint/index.js";
22
+ export type { Ja3Segments } from "./fingerprint/index.js";
23
+ export { nodeCrypto, nodeDns, nodeZlib, nodeHttp, toError, compareBytesOutcome, } from "./reference/node-reference.js";
24
+ export type { CompareOutcome, HashAlgorithm } from "./reference/node-reference.js";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhE,YAAY,EACR,UAAU,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,UAAU,EACV,UAAU,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,UAAU,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,WAAW,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGrE,OAAO,EACH,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,GAC7B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACR,sBAAsB,EACtB,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,GACxB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACjG,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAI1D,OAAO,EACH,UAAU,EACV,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,mBAAmB,GACtB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC"}