@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.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/bench/bench.d.ts +21 -0
- package/dist/bench/bench.d.ts.map +1 -0
- package/dist/bench/bench.js +19 -0
- package/dist/bench/bench.js.map +1 -0
- package/dist/captures/manifest.d.ts +18 -0
- package/dist/captures/manifest.d.ts.map +1 -0
- package/dist/captures/manifest.js +62 -0
- package/dist/captures/manifest.js.map +1 -0
- package/dist/categories/capture.d.ts +8 -0
- package/dist/categories/capture.d.ts.map +1 -0
- package/dist/categories/capture.js +31 -0
- package/dist/categories/capture.js.map +1 -0
- package/dist/categories/compression.d.ts +8 -0
- package/dist/categories/compression.d.ts.map +1 -0
- package/dist/categories/compression.js +15 -0
- package/dist/categories/compression.js.map +1 -0
- package/dist/categories/cookies.d.ts +8 -0
- package/dist/categories/cookies.d.ts.map +1 -0
- package/dist/categories/cookies.js +20 -0
- package/dist/categories/cookies.js.map +1 -0
- package/dist/categories/errors.d.ts +11 -0
- package/dist/categories/errors.d.ts.map +1 -0
- package/dist/categories/errors.js +41 -0
- package/dist/categories/errors.js.map +1 -0
- package/dist/categories/http.d.ts +9 -0
- package/dist/categories/http.d.ts.map +1 -0
- package/dist/categories/http.js +46 -0
- package/dist/categories/http.js.map +1 -0
- package/dist/categories/index.d.ts +17 -0
- package/dist/categories/index.d.ts.map +1 -0
- package/dist/categories/index.js +17 -0
- package/dist/categories/index.js.map +1 -0
- package/dist/categories/redirects.d.ts +8 -0
- package/dist/categories/redirects.d.ts.map +1 -0
- package/dist/categories/redirects.js +21 -0
- package/dist/categories/redirects.js.map +1 -0
- package/dist/categories/regression.d.ts +8 -0
- package/dist/categories/regression.d.ts.map +1 -0
- package/dist/categories/regression.js +28 -0
- package/dist/categories/regression.js.map +1 -0
- package/dist/categories/session.d.ts +8 -0
- package/dist/categories/session.d.ts.map +1 -0
- package/dist/categories/session.js +23 -0
- package/dist/categories/session.js.map +1 -0
- package/dist/categories/tls.d.ts +10 -0
- package/dist/categories/tls.d.ts.map +1 -0
- package/dist/categories/tls.js +48 -0
- package/dist/categories/tls.js.map +1 -0
- package/dist/categories/transport.d.ts +8 -0
- package/dist/categories/transport.d.ts.map +1 -0
- package/dist/categories/transport.js +22 -0
- package/dist/categories/transport.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/fingerprint/index.d.ts +5 -0
- package/dist/fingerprint/index.d.ts.map +1 -0
- package/dist/fingerprint/index.js +4 -0
- package/dist/fingerprint/index.js.map +1 -0
- package/dist/fingerprint/ja3.d.ts +36 -0
- package/dist/fingerprint/ja3.d.ts.map +1 -0
- package/dist/fingerprint/ja3.js +141 -0
- package/dist/fingerprint/ja3.js.map +1 -0
- package/dist/fingerprint/ja4.d.ts +109 -0
- package/dist/fingerprint/ja4.d.ts.map +1 -0
- package/dist/fingerprint/ja4.js +325 -0
- package/dist/fingerprint/ja4.js.map +1 -0
- package/dist/golden/golden.d.ts +45 -0
- package/dist/golden/golden.d.ts.map +1 -0
- package/dist/golden/golden.js +216 -0
- package/dist/golden/golden.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/reference/node-reference.d.ts +83 -0
- package/dist/reference/node-reference.d.ts.map +1 -0
- package/dist/reference/node-reference.js +161 -0
- package/dist/reference/node-reference.js.map +1 -0
- package/dist/reference/reference.d.ts +143 -0
- package/dist/reference/reference.d.ts.map +1 -0
- package/dist/reference/reference.js +323 -0
- package/dist/reference/reference.js.map +1 -0
- package/dist/rfc/rfcTests.d.ts +14 -0
- package/dist/rfc/rfcTests.d.ts.map +1 -0
- package/dist/rfc/rfcTests.js +22 -0
- package/dist/rfc/rfcTests.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +29 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +102 -0
- package/dist/utils.js.map +1 -0
- 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":"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"}
|
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|