@browsercore/testing 0.1.3 → 0.2.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/README.md +5 -0
- package/captures/DIVERGENCE_REPORT.md +325 -0
- package/captures/IMPERSIONATION_GAPS_REPORT.md +367 -0
- package/captures/README.md +56 -0
- package/captures/REVIEW_FINDINGS.md +71 -0
- package/captures/_probe/analyze.py +67 -0
- package/captures/_probe/capture_bytes.py +127 -0
- package/captures/_probe/chrome131_determinism.py +32 -0
- package/captures/_probe/diagnose_capture.py +59 -0
- package/captures/_probe/golden_analysis.py +91 -0
- package/captures/_probe/inspect_golden.py +55 -0
- package/captures/_probe/inspect_sv.py +16 -0
- package/captures/_probe/make_golden.py +170 -0
- package/captures/_probe/output/chrome100.json +219 -0
- package/captures/_probe/output/chrome101.json +219 -0
- package/captures/_probe/output/chrome104.json +219 -0
- package/captures/_probe/output/chrome107.json +220 -0
- package/captures/_probe/output/chrome110.json +220 -0
- package/captures/_probe/output/chrome116.json +220 -0
- package/captures/_probe/output/chrome119.json +219 -0
- package/captures/_probe/output/chrome120.json +219 -0
- package/captures/_probe/output/chrome123.json +223 -0
- package/captures/_probe/output/chrome124.json +224 -0
- package/captures/_probe/output/chrome131.json +224 -0
- package/captures/_probe/output/chrome131_fresh.json +224 -0
- package/captures/_probe/output/chrome133a.json +224 -0
- package/captures/_probe/output/chrome136.json +224 -0
- package/captures/_probe/output/chrome99.json +219 -0
- package/captures/_probe/output/edge101.json +219 -0
- package/captures/_probe/output/edge99.json +219 -0
- package/captures/_probe/output/firefox133.json +227 -0
- package/captures/_probe/output/firefox135.json +230 -0
- package/captures/_probe/output/safari155.json +210 -0
- package/captures/_probe/output/safari170.json +214 -0
- package/captures/_probe/output/safari170_fresh.json +214 -0
- package/captures/_probe/output/safari180.json +216 -0
- package/captures/_probe/output/safari180_ios.json +216 -0
- package/captures/_probe/output/safari184.json +215 -0
- package/captures/_probe/output/safari260.json +211 -0
- package/captures/_probe/output/summary.json +2520 -0
- package/captures/_probe/parse_existing_settings.py +15 -0
- package/captures/_probe/peetprint.py +39 -0
- package/captures/_probe/probe.py +137 -0
- package/captures/_probe/raw_bytes/chrome-131_client_hello.bin +0 -0
- package/captures/_probe/raw_bytes/chrome-131_client_hello.hex +32 -0
- package/captures/_probe/raw_bytes/firefox-133_client_hello.bin +0 -0
- package/captures/_probe/raw_bytes/firefox-133_client_hello.hex +32 -0
- package/captures/_probe/raw_bytes/safari-17_client_hello.bin +0 -0
- package/captures/_probe/raw_bytes/safari-17_client_hello.hex +32 -0
- package/captures/_probe/relay_capture.py +137 -0
- package/captures/_probe/socks_capture.py +138 -0
- package/captures/_probe/validate_fresh.py +66 -0
- package/captures/_probe/validate_golden.py +97 -0
- package/captures/_probe/verify_capture.py +68 -0
- package/captures/chrome-131/http2/settings.bin +0 -0
- package/captures/chrome-131/http2/settings.meta.json +9 -0
- package/captures/chrome-131/oracle_capture.reference.json +224 -0
- package/captures/chrome-131/tls/client_hello.bin +0 -0
- package/captures/chrome-131/tls/client_hello.meta.json +20 -0
- package/captures/chrome-140/http2/settings.bin +0 -0
- package/captures/chrome-140/http2/settings.meta.json +9 -0
- package/captures/chrome-140/tls/client_hello.bin +0 -0
- package/captures/chrome-140/tls/client_hello.meta.json +12 -0
- package/captures/firefox-128/tls/client_hello.bin +0 -0
- package/captures/firefox-128/tls/client_hello.meta.json +12 -0
- package/captures/firefox-133/http2/settings.bin +0 -0
- package/captures/firefox-133/http2/settings.meta.json +9 -0
- package/captures/firefox-133/oracle_capture.reference.json +227 -0
- package/captures/firefox-133/tls/client_hello.bin +0 -0
- package/captures/firefox-133/tls/client_hello.meta.json +20 -0
- package/captures/safari-17/http2/settings.bin +0 -0
- package/captures/safari-17/http2/settings.meta.json +9 -0
- package/captures/safari-17/oracle_capture.reference.json +214 -0
- package/captures/safari-17/tls/client_hello.bin +0 -0
- package/captures/safari-17/tls/client_hello.meta.json +20 -0
- package/dist/bench/bench.d.ts +22 -6
- package/dist/bench/bench.d.ts.map +1 -1
- package/dist/bench/bench.js +105 -10
- package/dist/bench/bench.js.map +1 -1
- package/dist/captures/manifest.d.ts.map +1 -1
- package/dist/captures/manifest.js +2 -3
- package/dist/captures/manifest.js.map +1 -1
- package/dist/categories/cookies.d.ts +3 -2
- package/dist/categories/cookies.d.ts.map +1 -1
- package/dist/categories/cookies.js +228 -11
- package/dist/categories/cookies.js.map +1 -1
- package/dist/categories/redirects.d.ts +8 -2
- package/dist/categories/redirects.d.ts.map +1 -1
- package/dist/categories/redirects.js +97 -12
- package/dist/categories/redirects.js.map +1 -1
- package/dist/e2e/cert-gen.d.ts +30 -0
- package/dist/e2e/cert-gen.d.ts.map +1 -0
- package/dist/e2e/cert-gen.js +158 -0
- package/dist/e2e/cert-gen.js.map +1 -0
- package/dist/e2e/parse-clienthello.d.ts +177 -0
- package/dist/e2e/parse-clienthello.d.ts.map +1 -0
- package/dist/e2e/parse-clienthello.js +343 -0
- package/dist/e2e/parse-clienthello.js.map +1 -0
- package/dist/e2e/sink-server.d.ts +109 -0
- package/dist/e2e/sink-server.d.ts.map +1 -0
- package/dist/e2e/sink-server.js +292 -0
- package/dist/e2e/sink-server.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fingerprint/index.d.ts +5 -2
- package/dist/fingerprint/index.d.ts.map +1 -1
- package/dist/fingerprint/index.js +3 -2
- package/dist/fingerprint/index.js.map +1 -1
- package/dist/fingerprint/ja3.d.ts.map +1 -1
- package/dist/fingerprint/ja3.js +33 -4
- package/dist/fingerprint/ja3.js.map +1 -1
- package/dist/fingerprint/ja4-errors.d.ts +17 -0
- package/dist/fingerprint/ja4-errors.d.ts.map +1 -0
- package/dist/fingerprint/ja4-errors.js +20 -0
- package/dist/fingerprint/ja4-errors.js.map +1 -0
- package/dist/fingerprint/ja4-reader.d.ts +44 -0
- package/dist/fingerprint/ja4-reader.d.ts.map +1 -0
- package/dist/fingerprint/ja4-reader.js +112 -0
- package/dist/fingerprint/ja4-reader.js.map +1 -0
- package/dist/fingerprint/ja4.d.ts +11 -43
- package/dist/fingerprint/ja4.d.ts.map +1 -1
- package/dist/fingerprint/ja4.js +54 -141
- package/dist/fingerprint/ja4.js.map +1 -1
- package/dist/fingerprint/ja4h.d.ts +46 -0
- package/dist/fingerprint/ja4h.d.ts.map +1 -0
- package/dist/fingerprint/ja4h.js +50 -0
- package/dist/fingerprint/ja4h.js.map +1 -0
- package/dist/golden/golden.d.ts +15 -1
- package/dist/golden/golden.d.ts.map +1 -1
- package/dist/golden/golden.js +35 -22
- package/dist/golden/golden.js.map +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/reference/browser-provider.d.ts +33 -0
- package/dist/reference/browser-provider.d.ts.map +1 -0
- package/dist/reference/browser-provider.js +60 -0
- package/dist/reference/browser-provider.js.map +1 -0
- package/dist/reference/curl-provider.d.ts +40 -0
- package/dist/reference/curl-provider.d.ts.map +1 -0
- package/dist/reference/curl-provider.js +96 -0
- package/dist/reference/curl-provider.js.map +1 -0
- package/dist/reference/dump.d.ts +42 -0
- package/dist/reference/dump.d.ts.map +1 -0
- package/dist/reference/dump.js +102 -0
- package/dist/reference/dump.js.map +1 -0
- package/dist/reference/node-reference.d.ts.map +1 -1
- package/dist/reference/node-reference.js +2 -5
- package/dist/reference/node-reference.js.map +1 -1
- package/dist/reference/reference-errors.d.ts +36 -0
- package/dist/reference/reference-errors.d.ts.map +1 -0
- package/dist/reference/reference-errors.js +43 -0
- package/dist/reference/reference-errors.js.map +1 -0
- package/dist/reference/reference-types.d.ts +33 -0
- package/dist/reference/reference-types.d.ts.map +1 -0
- package/dist/reference/reference-types.js +9 -0
- package/dist/reference/reference-types.js.map +1 -0
- package/dist/reference/reference.d.ts +14 -77
- package/dist/reference/reference.d.ts.map +1 -1
- package/dist/reference/reference.js +43 -207
- package/dist/reference/reference.js.map +1 -1
- package/dist/rfc/rfcTests.d.ts +46 -8
- package/dist/rfc/rfcTests.d.ts.map +1 -1
- package/dist/rfc/rfcTests.js +203 -16
- package/dist/rfc/rfcTests.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +11 -5
- package/dist/utils.js.map +1 -1
- package/package.json +61 -55
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pluggable layered reference provider.
|
|
2
|
+
* Pluggable layered reference provider — facade + factory.
|
|
3
3
|
*
|
|
4
4
|
* The reference implementation is the source of truth for what a given browser
|
|
5
5
|
* profile SHOULD produce on the wire. Three backends are wrapped behind one
|
|
@@ -17,129 +17,19 @@
|
|
|
17
17
|
* primary then secondary; fingerprint is derived from the captured bytes; the
|
|
18
18
|
* node-reference oracle is exposed for primitive-layer comparisons.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* The providers and dump helpers live in their own modules; this file owns the
|
|
21
|
+
* facade + factory dispatch. See docs/TEST-SUITE.md (Cat 3, 4, 14).
|
|
21
22
|
*/
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const packageRoot = join(here, "..", "..");
|
|
33
|
-
const defaultCapturesDir = join(packageRoot, "captures");
|
|
34
|
-
/** Raised when a reference provider cannot capture or fingerprint a profile. */
|
|
35
|
-
export class ReferenceError extends TestingError {
|
|
36
|
-
constructor(message, options) {
|
|
37
|
-
super(message, options);
|
|
38
|
-
this.name = "ReferenceError";
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* PRIMARY provider — shells out to curl-impersonate.
|
|
43
|
-
*
|
|
44
|
-
* `capture()` invokes the curl-impersonate binary against `url`, impersonating
|
|
45
|
-
* `profile`, and returns the raw bytes it observed on the wire. The binary
|
|
46
|
-
* must be on PATH (or in `command`); if it is missing, the call throws
|
|
47
|
-
* {@link ReferenceError} so the facade can fall back to the secondary.
|
|
48
|
-
*/
|
|
49
|
-
export class CurlImpersonateProvider {
|
|
50
|
-
kind = { kind: "curl-impersonate" };
|
|
51
|
-
command;
|
|
52
|
-
extraArgs;
|
|
53
|
-
constructor(options) {
|
|
54
|
-
this.command = options?.command ?? "curl-impersonate";
|
|
55
|
-
this.extraArgs = options?.extraArgs ?? [];
|
|
56
|
-
}
|
|
57
|
-
availableProfiles() {
|
|
58
|
-
// curl-impersonate ships these browser impersonation targets.
|
|
59
|
-
return [
|
|
60
|
-
"chrome-140",
|
|
61
|
-
"chrome-139",
|
|
62
|
-
"firefox-135",
|
|
63
|
-
"firefox-128",
|
|
64
|
-
"safari-18",
|
|
65
|
-
"edge-140",
|
|
66
|
-
];
|
|
67
|
-
}
|
|
68
|
-
async capture(profile, url) {
|
|
69
|
-
const profileFlag = `--${String(profile)}`;
|
|
70
|
-
const args = [profileFlag, "--dump-traffic", ...this.extraArgs, url];
|
|
71
|
-
let stdout;
|
|
72
|
-
let stderr;
|
|
73
|
-
try {
|
|
74
|
-
const out = await execFileAsync(this.command, args, {
|
|
75
|
-
timeout: 30_000,
|
|
76
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
77
|
-
});
|
|
78
|
-
stdout = out.stdout;
|
|
79
|
-
stderr = out.stderr;
|
|
80
|
-
}
|
|
81
|
-
catch (e) {
|
|
82
|
-
const cause = e instanceof Error ? e : new Error(String(e));
|
|
83
|
-
throw new ReferenceError(`curl-impersonate capture for ${String(profile)} failed: ${cause.message}`, { cause });
|
|
84
|
-
}
|
|
85
|
-
void stderr;
|
|
86
|
-
const bytes = parseDumpOutput(stdout);
|
|
87
|
-
return {
|
|
88
|
-
id: `${profile}/tls/client_hello`,
|
|
89
|
-
source: profileToSource(profile),
|
|
90
|
-
protocol: "tls",
|
|
91
|
-
bytes,
|
|
92
|
-
description: `curl-impersonate capture for ${String(profile)}`,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
async fingerprint(profile) {
|
|
96
|
-
const capture = await this.capture(profile, "https://example.com");
|
|
97
|
-
return fingerprintFromTlsCapture(capture);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* SECONDARY provider — loads pre-recorded captures from the `captures/` dir.
|
|
102
|
-
*
|
|
103
|
-
* `capture()` resolves a stored capture for the profile; `fingerprint()`
|
|
104
|
-
* derives a {@link Fingerprint} from the captured bytes (only TLS
|
|
105
|
-
* ClientHellos are fingerprinted — other protocols return a stub).
|
|
106
|
-
*/
|
|
107
|
-
export class RealBrowserCaptureProvider {
|
|
108
|
-
kind = { kind: "real-browser" };
|
|
109
|
-
capturesDir;
|
|
110
|
-
constructor(options) {
|
|
111
|
-
this.capturesDir = options?.capturesDir ?? defaultCapturesDir;
|
|
112
|
-
}
|
|
113
|
-
availableProfiles() {
|
|
114
|
-
// Discovery is driven by the captures manifest (captures/manifest.ts).
|
|
115
|
-
return [
|
|
116
|
-
"chrome-140",
|
|
117
|
-
"firefox-128",
|
|
118
|
-
];
|
|
119
|
-
}
|
|
120
|
-
async capture(profile, _url) {
|
|
121
|
-
void _url;
|
|
122
|
-
const manifest = await import("../captures/manifest.js");
|
|
123
|
-
const entry = manifest.captures.find((c) => c.meta.profile === profile);
|
|
124
|
-
if (entry === undefined) {
|
|
125
|
-
throw new ReferenceError(`No pre-recorded capture for profile ${String(profile)}`);
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
id: `${profile}/tls/client_hello`,
|
|
129
|
-
source: profileToSource(profile),
|
|
130
|
-
protocol: entry.meta.protocol,
|
|
131
|
-
bytes: entry.bytes,
|
|
132
|
-
description: entry.meta.description,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
async fingerprint(profile) {
|
|
136
|
-
const capture = await this.capture(profile, "https://example.com");
|
|
137
|
-
if (capture.protocol !== "tls") {
|
|
138
|
-
throw new ReferenceError(`fingerprint() only supports TLS captures; got ${capture.protocol}`);
|
|
139
|
-
}
|
|
140
|
-
return fingerprintFromTlsCapture(capture);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
23
|
+
import { CurlImpersonateProvider } from "./curl-provider.js";
|
|
24
|
+
import { RealBrowserCaptureProvider } from "./browser-provider.js";
|
|
25
|
+
import { ReferenceError } from "./reference-errors.js";
|
|
26
|
+
import { assertNever } from "../utils.js";
|
|
27
|
+
// Re-export so existing import sites (`from "./reference.js"`) keep resolving
|
|
28
|
+
// after the providers and helpers moved into focused modules.
|
|
29
|
+
export { CurlImpersonateProvider } from "./curl-provider.js";
|
|
30
|
+
export { RealBrowserCaptureProvider } from "./browser-provider.js";
|
|
31
|
+
export { ReferenceError, DumpParseError } from "./reference-errors.js";
|
|
32
|
+
export { parseDumpOutput, fingerprintFromTlsCapture, cipherSuiteName } from "./dump.js";
|
|
143
33
|
/**
|
|
144
34
|
* Facade wrapping the node-reference oracle + primary (curl-impersonate) and
|
|
145
35
|
* secondary (real-browser) providers behind a single {@link ReferenceProvider}.
|
|
@@ -172,8 +62,9 @@ export class ReferenceProviderFacade {
|
|
|
172
62
|
const out = [];
|
|
173
63
|
for (const p of [...this.primary.availableProfiles(), ...this.secondary.availableProfiles()]) {
|
|
174
64
|
const key = String(p);
|
|
175
|
-
if (seen.has(key))
|
|
65
|
+
if (seen.has(key)) {
|
|
176
66
|
continue;
|
|
67
|
+
}
|
|
177
68
|
seen.add(key);
|
|
178
69
|
out.push(p);
|
|
179
70
|
}
|
|
@@ -221,103 +112,48 @@ import * as nodeOracle from "./node-reference.js";
|
|
|
221
112
|
export function createReferenceProvider(kind, options) {
|
|
222
113
|
switch (kind.kind) {
|
|
223
114
|
case "curl-impersonate":
|
|
224
|
-
return new CurlImpersonateProvider(options);
|
|
115
|
+
return new CurlImpersonateProvider(toCurlOptions(options));
|
|
225
116
|
case "real-browser":
|
|
226
|
-
return new RealBrowserCaptureProvider(options);
|
|
117
|
+
return new RealBrowserCaptureProvider(toBrowserOptions(options));
|
|
118
|
+
default:
|
|
119
|
+
// Exhaustiveness guaranteed by the union — unreachable unless a new
|
|
120
|
+
// provider kind is added without a handler.
|
|
121
|
+
return assertNever(kind);
|
|
227
122
|
}
|
|
228
123
|
}
|
|
229
124
|
/**
|
|
230
|
-
*
|
|
231
|
-
* plus the node-reference oracle. This is the recommended entry point for
|
|
232
|
-
* tests that want primary→secondary fallback.
|
|
233
|
-
*/
|
|
234
|
-
export function createReferenceFacade(options) {
|
|
235
|
-
return new ReferenceProviderFacade(options);
|
|
236
|
-
}
|
|
237
|
-
/** Map a {@link ProfileId} to its {@link CaptureSource} tag. */
|
|
238
|
-
function profileToSource(profile) {
|
|
239
|
-
const p = String(profile);
|
|
240
|
-
if (p.startsWith("firefox"))
|
|
241
|
-
return "firefox-135";
|
|
242
|
-
if (p.startsWith("safari"))
|
|
243
|
-
return "safari-18";
|
|
244
|
-
if (p.startsWith("edge"))
|
|
245
|
-
return "edge-140";
|
|
246
|
-
return "chrome-140";
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Parse curl-impersonate `--dump-traffic` output into raw bytes.
|
|
125
|
+
* Narrow the shared options union to the curl-impersonate shape.
|
|
250
126
|
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
127
|
+
* Copies only the fields the curl provider reads, so passing a
|
|
128
|
+
* browser-options object does not leak `capturesDir` into a place that ignores
|
|
129
|
+
* it. No `as` cast: the result is built field-by-field from validated reads.
|
|
254
130
|
*/
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const idx = stdout.indexOf(marker);
|
|
259
|
-
const body = idx === -1 ? stdout : stdout.slice(idx + marker.length);
|
|
260
|
-
const hex = body.replace(/[^0-9a-fA-F]/g, "");
|
|
261
|
-
if (hex.length === 0) {
|
|
262
|
-
throw new ReferenceError("curl-impersonate dump produced no hex bytes");
|
|
263
|
-
}
|
|
264
|
-
if (hex.length % 2 !== 0) {
|
|
265
|
-
throw new ReferenceError(`curl-impersonate dump produced odd-length hex (${hex.length})`);
|
|
131
|
+
function toCurlOptions(options) {
|
|
132
|
+
if (options === undefined || !("command" in options) || options.command === undefined) {
|
|
133
|
+
return undefined;
|
|
266
134
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
return bytes;
|
|
135
|
+
return options.extraArgs === undefined
|
|
136
|
+
? { command: options.command }
|
|
137
|
+
: { command: options.command, extraArgs: options.extraArgs };
|
|
272
138
|
}
|
|
273
139
|
/**
|
|
274
|
-
*
|
|
140
|
+
* Narrow the shared options union to the real-browser shape.
|
|
275
141
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* `.meta.json` when available.
|
|
142
|
+
* Copies only the fields the browser provider reads. `command` is `undefined`
|
|
143
|
+
* on `RealBrowserOptions` by construction, so it is not copied.
|
|
279
144
|
*/
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// Read the sidecar meta for richer fields (signature algorithms, ALPN,
|
|
284
|
-
// supported curves). Fall back to empty arrays if missing.
|
|
285
|
-
let alpn = [];
|
|
286
|
-
let signatureAlgorithms = [];
|
|
287
|
-
let ellipticCurves = [];
|
|
288
|
-
try {
|
|
289
|
-
const meta = loadCaptureMeta(capture.id);
|
|
290
|
-
if (meta.protocol === "tls") {
|
|
291
|
-
// CaptureMeta doesn't carry ALPN/sigAlgs/curves yet; this is a
|
|
292
|
-
// placeholder for when the sidecar schema is extended.
|
|
293
|
-
void meta;
|
|
294
|
-
void alpn;
|
|
295
|
-
void signatureAlgorithms;
|
|
296
|
-
void ellipticCurves;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
// Sidecar missing or unparseable — leave richer fields empty.
|
|
145
|
+
function toBrowserOptions(options) {
|
|
146
|
+
if (options === undefined || !("capturesDir" in options) || options.capturesDir === undefined) {
|
|
147
|
+
return undefined;
|
|
301
148
|
}
|
|
302
|
-
return {
|
|
303
|
-
ja3,
|
|
304
|
-
ja4: ja4.tag,
|
|
305
|
-
alpn,
|
|
306
|
-
cipherSuite: cipherSuiteName(ja4.tag),
|
|
307
|
-
protocolVersion: ja4.tag.slice(5, 7) || "unknown",
|
|
308
|
-
signatureAlgorithms,
|
|
309
|
-
ellipticCurves,
|
|
310
|
-
};
|
|
149
|
+
return { capturesDir: options.capturesDir };
|
|
311
150
|
}
|
|
312
151
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* that returns the JA4_a segment for inspection. Real cipher-suite resolution
|
|
317
|
-
* requires parsing the ServerHello, which is out of scope for the capture.
|
|
152
|
+
* Construct the full {@link ReferenceProviderFacade} wrapping both providers
|
|
153
|
+
* plus the node-reference oracle. This is the recommended entry point for
|
|
154
|
+
* tests that want primary→secondary fallback.
|
|
318
155
|
*/
|
|
319
|
-
function
|
|
320
|
-
|
|
321
|
-
return a.length > 0 ? a : "unknown";
|
|
156
|
+
export function createReferenceFacade(options) {
|
|
157
|
+
return new ReferenceProviderFacade(options);
|
|
322
158
|
}
|
|
323
159
|
//# sourceMappingURL=reference.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../src/reference/reference.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../src/reference/reference.ts"],"names":[],"mappings":"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"}
|
package/dist/rfc/rfcTests.d.ts
CHANGED
|
@@ -1,14 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RFC compliance test suites.
|
|
3
3
|
*
|
|
4
|
-
* Each function runs a focused compliance check
|
|
5
|
-
*
|
|
4
|
+
* Each function runs a focused compliance check against the in-repo golden
|
|
5
|
+
* captures and the @browsercore/profiles definitions, and returns a
|
|
6
|
+
* {@link TestResult}. The checks are deterministic and need no live network:
|
|
7
|
+
*
|
|
8
|
+
* - TLS (RFC 8446) — parse the golden ClientHello for the active profile,
|
|
9
|
+
* validate cipher order / extensions / GREASE / signature algorithms /
|
|
10
|
+
* supported versions against the profile definition via
|
|
11
|
+
* {@link validateProfileAgainstCapture}.
|
|
12
|
+
* - HTTP/2 (RFC 9113) — parse the golden SETTINGS frame and verify the
|
|
13
|
+
* settings values match the profile's {@link Http2Profile.settings}.
|
|
14
|
+
* - HTTP/1.1 (RFC 9110) — verify the profile's header order and default
|
|
15
|
+
* headers form a valid HTTP/1.1 request block (no duplicate pseudo-headers,
|
|
16
|
+
* required Host header present, header names lowercase).
|
|
6
17
|
*/
|
|
18
|
+
import { type ProfileId } from "@browsercore/profiles";
|
|
7
19
|
import type { TestCaseId, TestResult } from "../types.js";
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Run the TLS 1.3 (RFC 8446) compliance checks against the chrome-140
|
|
22
|
+
* golden capture. Validates cipher order, extension order, GREASE handling,
|
|
23
|
+
* and supported groups against the chrome-140 profile definition.
|
|
24
|
+
*
|
|
25
|
+
* Returns a {@link TestResult} that aggregates the per-field diffs reported
|
|
26
|
+
* by {@link validateProfileAgainstCapture}.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runTlsCompliance(id?: TestCaseId, profileId?: ProfileId): TestResult;
|
|
29
|
+
/**
|
|
30
|
+
* Run the HTTP/2 (RFC 9113) compliance checks against the chrome-140
|
|
31
|
+
* golden SETTINGS capture. Verifies each SETTINGS value matches the
|
|
32
|
+
* profile's {@link Http2Profile.settings}.
|
|
33
|
+
*
|
|
34
|
+
* SETTINGS frame ids (RFC 9113 §6.5.1):
|
|
35
|
+
* 1 = HEADER_TABLE_SIZE
|
|
36
|
+
* 2 = ENABLE_PUSH
|
|
37
|
+
* 3 = MAX_CONCURRENT_STREAMS
|
|
38
|
+
* 4 = INITIAL_WINDOW_SIZE
|
|
39
|
+
* 5 = MAX_FRAME_SIZE
|
|
40
|
+
*/
|
|
41
|
+
export declare function runHttp2Compliance(id?: TestCaseId, profileId?: ProfileId): TestResult;
|
|
42
|
+
/**
|
|
43
|
+
* Run the HTTP/1.1 (RFC 9110) compliance checks against the chrome-140
|
|
44
|
+
* profile's header configuration. Verifies:
|
|
45
|
+
*
|
|
46
|
+
* 1. The `Host` header is present (required by RFC 9110 §7.2).
|
|
47
|
+
* 2. Header names are lowercase (HTTP/2 requires this; HTTP/1.1 convention).
|
|
48
|
+
* 3. No duplicate header names in the default set.
|
|
49
|
+
* 4. The header order list matches the default headers' key set.
|
|
50
|
+
*/
|
|
51
|
+
export declare function runHttp1Compliance(id?: TestCaseId, profileId?: ProfileId): TestResult;
|
|
14
52
|
//# sourceMappingURL=rfcTests.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rfcTests.d.ts","sourceRoot":"","sources":["../../src/rfc/rfcTests.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rfcTests.d.ts","sourceRoot":"","sources":["../../src/rfc/rfcTests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAA6C,KAAK,SAAS,EAAmB,MAAM,uBAAuB,CAAC;AACnH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAyF1D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC5B,EAAE,GAAE,UAA+B,EACnC,SAAS,GAAE,SAAqC,GACjD,UAAU,CAeZ;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAC9B,EAAE,GAAE,UAAiC,EACrC,SAAS,GAAE,SAAqC,GACjD,UAAU,CAyCZ;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,EAAE,GAAE,UAAiC,EACrC,SAAS,GAAE,SAAqC,GACjD,UAAU,CA0DZ"}
|
package/dist/rfc/rfcTests.js
CHANGED
|
@@ -1,22 +1,209 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RFC compliance test suites.
|
|
3
3
|
*
|
|
4
|
-
* Each function runs a focused compliance check
|
|
5
|
-
*
|
|
4
|
+
* Each function runs a focused compliance check against the in-repo golden
|
|
5
|
+
* captures and the @browsercore/profiles definitions, and returns a
|
|
6
|
+
* {@link TestResult}. The checks are deterministic and need no live network:
|
|
7
|
+
*
|
|
8
|
+
* - TLS (RFC 8446) — parse the golden ClientHello for the active profile,
|
|
9
|
+
* validate cipher order / extensions / GREASE / signature algorithms /
|
|
10
|
+
* supported versions against the profile definition via
|
|
11
|
+
* {@link validateProfileAgainstCapture}.
|
|
12
|
+
* - HTTP/2 (RFC 9113) — parse the golden SETTINGS frame and verify the
|
|
13
|
+
* settings values match the profile's {@link Http2Profile.settings}.
|
|
14
|
+
* - HTTP/1.1 (RFC 9110) — verify the profile's header order and default
|
|
15
|
+
* headers form a valid HTTP/1.1 request block (no duplicate pseudo-headers,
|
|
16
|
+
* required Host header present, header names lowercase).
|
|
17
|
+
*/
|
|
18
|
+
import { getProfile, validateProfileAgainstCapture } from "@browsercore/profiles";
|
|
19
|
+
import { parseClientHello } from "../fingerprint/index.js";
|
|
20
|
+
import { loadGolden } from "../golden/golden.js";
|
|
21
|
+
/** Split a dash-joined numeric string into an integer array. */
|
|
22
|
+
function parts(s) {
|
|
23
|
+
return s.length === 0 ? [] : s.split("-").map((n) => Math.trunc(Number(n)));
|
|
24
|
+
}
|
|
25
|
+
/** Parse a golden TLS ClientHello capture into a {@link TlsCapture}. */
|
|
26
|
+
function parseTlsCapture(bytes) {
|
|
27
|
+
const segs = parseClientHello(bytes);
|
|
28
|
+
// GREASE detection: the capture advertises GREASE iff its cipher list
|
|
29
|
+
// contains a GREASE-pattern value (0x?a?a in 0x0a0a..0xfafa).
|
|
30
|
+
const ciphers = parts(segs.ciphers);
|
|
31
|
+
const grease = ciphers.some((c) => c >= 0x0a0a && (c >> 8) === (c & 0xff));
|
|
32
|
+
return {
|
|
33
|
+
cipherSuites: ciphers,
|
|
34
|
+
extensionTypes: parts(segs.extensions),
|
|
35
|
+
supportedVersions: [],
|
|
36
|
+
keyShareGroups: parts(segs.supportedGroups),
|
|
37
|
+
signatureAlgorithms: [],
|
|
38
|
+
grease,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse a raw HTTP/2 SETTINGS frame payload (6 bytes per setting: 2-byte id +
|
|
43
|
+
* 4-byte value) into a map of setting id → value.
|
|
44
|
+
*/
|
|
45
|
+
function parseHttp2Settings(payload) {
|
|
46
|
+
const settings = new Map();
|
|
47
|
+
for (let i = 0; i + 5 < payload.length; i += 6) {
|
|
48
|
+
const b0 = payload[i] ?? 0;
|
|
49
|
+
const b1 = payload[i + 1] ?? 0;
|
|
50
|
+
const b2 = payload[i + 2] ?? 0;
|
|
51
|
+
const b3 = payload[i + 3] ?? 0;
|
|
52
|
+
const b4 = payload[i + 4] ?? 0;
|
|
53
|
+
const b5 = payload[i + 5] ?? 0;
|
|
54
|
+
const id = (b0 << 8) | b1;
|
|
55
|
+
const value = (b2 << 24) | (b3 << 16) | (b4 << 8) | b5;
|
|
56
|
+
settings.set(id, value >>> 0); // unsigned
|
|
57
|
+
}
|
|
58
|
+
return settings;
|
|
59
|
+
}
|
|
60
|
+
/** HTTP/2 SETTINGS frame type id (RFC 9113 §6.5). */
|
|
61
|
+
const HTTP2_FRAME_TYPE_SETTINGS = 0x4;
|
|
62
|
+
/** HTTP/2 frame header size: 3-byte length + 1-byte type + 1-byte flags + 4-byte stream id. */
|
|
63
|
+
const HTTP2_FRAME_HEADER_SIZE = 9;
|
|
64
|
+
/** Strip the frame header (9 bytes) and return the settings payload. */
|
|
65
|
+
function settingsPayload(captureBytes) {
|
|
66
|
+
// A bare SETTINGS frame has no TLS record wrapper — it starts with the
|
|
67
|
+
// 9-byte HTTP/2 frame header. Byte layout:
|
|
68
|
+
// [0..2] payload length (24 bits)
|
|
69
|
+
// [3] frame type
|
|
70
|
+
// [4] flags
|
|
71
|
+
// [5..8] stream id (31 bits, top bit reserved)
|
|
72
|
+
// Validate the frame type before slicing.
|
|
73
|
+
if (captureBytes.length < HTTP2_FRAME_HEADER_SIZE) {
|
|
74
|
+
return captureBytes;
|
|
75
|
+
}
|
|
76
|
+
const frameType = captureBytes[3];
|
|
77
|
+
if (frameType !== HTTP2_FRAME_TYPE_SETTINGS) {
|
|
78
|
+
// Not a SETTINGS frame at byte 3 — return the raw bytes for the caller
|
|
79
|
+
// to surface as a compliance failure.
|
|
80
|
+
return captureBytes;
|
|
81
|
+
}
|
|
82
|
+
return captureBytes.slice(HTTP2_FRAME_HEADER_SIZE);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build a {@link TestResult} from a validation outcome.
|
|
86
|
+
* `actual` / `expected` carry the parsed capture values for diagnostics.
|
|
87
|
+
* Omits `diff` entirely when there is nothing to report (exactOptionalPropertyTypes).
|
|
88
|
+
*/
|
|
89
|
+
function result(id, pass, actual, expected, diff) {
|
|
90
|
+
return diff === undefined ? { id, pass, actual, expected } : { id, pass, actual, expected, diff };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run the TLS 1.3 (RFC 8446) compliance checks against the chrome-140
|
|
94
|
+
* golden capture. Validates cipher order, extension order, GREASE handling,
|
|
95
|
+
* and supported groups against the chrome-140 profile definition.
|
|
96
|
+
*
|
|
97
|
+
* Returns a {@link TestResult} that aggregates the per-field diffs reported
|
|
98
|
+
* by {@link validateProfileAgainstCapture}.
|
|
99
|
+
*/
|
|
100
|
+
export function runTlsCompliance(id = "tls_rfc", profileId = "chrome-140") {
|
|
101
|
+
const profile = getProfile(profileId);
|
|
102
|
+
const capture = loadGolden(`${profileId}/tls/client_hello`);
|
|
103
|
+
const tlsCapture = parseTlsCapture(capture.bytes);
|
|
104
|
+
const validation = validateProfileAgainstCapture(profile, tlsCapture);
|
|
105
|
+
return result(id, validation.ok, tlsCapture, "profile matches golden capture", validation.ok
|
|
106
|
+
? undefined
|
|
107
|
+
: validation.diffs.map((d) => `${d.path}: ${String(d.a)} vs ${String(d.b)}`).join("; "));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Run the HTTP/2 (RFC 9113) compliance checks against the chrome-140
|
|
111
|
+
* golden SETTINGS capture. Verifies each SETTINGS value matches the
|
|
112
|
+
* profile's {@link Http2Profile.settings}.
|
|
113
|
+
*
|
|
114
|
+
* SETTINGS frame ids (RFC 9113 §6.5.1):
|
|
115
|
+
* 1 = HEADER_TABLE_SIZE
|
|
116
|
+
* 2 = ENABLE_PUSH
|
|
117
|
+
* 3 = MAX_CONCURRENT_STREAMS
|
|
118
|
+
* 4 = INITIAL_WINDOW_SIZE
|
|
119
|
+
* 5 = MAX_FRAME_SIZE
|
|
120
|
+
*/
|
|
121
|
+
export function runHttp2Compliance(id = "http2_rfc", profileId = "chrome-140") {
|
|
122
|
+
const profile = getProfile(profileId);
|
|
123
|
+
const capture = loadGolden(`${profileId}/http2/settings`);
|
|
124
|
+
const payload = settingsPayload(capture.bytes);
|
|
125
|
+
const settings = parseHttp2Settings(payload);
|
|
126
|
+
// RFC 9113 §6.5.1 setting ids.
|
|
127
|
+
const SETTING_MAX_CONCURRENT_STREAMS = 3;
|
|
128
|
+
const SETTING_INITIAL_WINDOW_SIZE = 4;
|
|
129
|
+
const SETTING_MAX_FRAME_SIZE = 5;
|
|
130
|
+
const expected = profile.http2.settings;
|
|
131
|
+
const checks = [
|
|
132
|
+
{
|
|
133
|
+
name: "MAX_CONCURRENT_STREAMS",
|
|
134
|
+
ok: settings.get(SETTING_MAX_CONCURRENT_STREAMS) === expected.maxConcurrentStreams,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "INITIAL_WINDOW_SIZE",
|
|
138
|
+
ok: settings.get(SETTING_INITIAL_WINDOW_SIZE) === expected.initialWindowSize,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "MAX_FRAME_SIZE",
|
|
142
|
+
ok: settings.get(SETTING_MAX_FRAME_SIZE) === expected.maxFrameSize,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
const failed = checks.filter((c) => !c.ok);
|
|
146
|
+
return result(id, failed.length === 0, Object.fromEntries(settings), {
|
|
147
|
+
maxConcurrentStreams: expected.maxConcurrentStreams,
|
|
148
|
+
initialWindowSize: expected.initialWindowSize,
|
|
149
|
+
maxFrameSize: expected.maxFrameSize,
|
|
150
|
+
}, failed.length === 0
|
|
151
|
+
? undefined
|
|
152
|
+
: `Mismatched settings: ${failed.map((f) => f.name).join(", ")}`);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Run the HTTP/1.1 (RFC 9110) compliance checks against the chrome-140
|
|
156
|
+
* profile's header configuration. Verifies:
|
|
157
|
+
*
|
|
158
|
+
* 1. The `Host` header is present (required by RFC 9110 §7.2).
|
|
159
|
+
* 2. Header names are lowercase (HTTP/2 requires this; HTTP/1.1 convention).
|
|
160
|
+
* 3. No duplicate header names in the default set.
|
|
161
|
+
* 4. The header order list matches the default headers' key set.
|
|
6
162
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
163
|
+
export function runHttp1Compliance(id = "http1_rfc", profileId = "chrome-140") {
|
|
164
|
+
const profile = getProfile(profileId);
|
|
165
|
+
const headers = profile.http1.defaultHeaders;
|
|
166
|
+
const headerOrder = profile.http1.headerOrder;
|
|
167
|
+
const issues = [];
|
|
168
|
+
// 1. Host header required (RFC 9110 §7.2). The Host header is set
|
|
169
|
+
// dynamically per-request (it depends on the target URL), so it appears
|
|
170
|
+
// in headerOrder but not in defaultHeaders. Check headerOrder for it.
|
|
171
|
+
const hasHost = headerOrder.some((h) => h.toLowerCase() === "host") ||
|
|
172
|
+
"host" in headers ||
|
|
173
|
+
"Host" in headers;
|
|
174
|
+
if (!hasHost) {
|
|
175
|
+
issues.push("missing required Host header");
|
|
176
|
+
}
|
|
177
|
+
// 2. Header names must be lowercase.
|
|
178
|
+
const nonLowercase = headerOrder.filter((h) => h !== h.toLowerCase());
|
|
179
|
+
if (nonLowercase.length > 0) {
|
|
180
|
+
issues.push(`non-lowercase header names: ${nonLowercase.join(", ")}`);
|
|
181
|
+
}
|
|
182
|
+
// 3. No duplicate header names in the default set.
|
|
183
|
+
const seen = new Set();
|
|
184
|
+
const duplicates = [];
|
|
185
|
+
for (const key of Object.keys(headers)) {
|
|
186
|
+
const lower = key.toLowerCase();
|
|
187
|
+
if (seen.has(lower)) {
|
|
188
|
+
duplicates.push(key);
|
|
189
|
+
}
|
|
190
|
+
seen.add(lower);
|
|
191
|
+
}
|
|
192
|
+
if (duplicates.length > 0) {
|
|
193
|
+
issues.push(`duplicate header names: ${duplicates.join(", ")}`);
|
|
194
|
+
}
|
|
195
|
+
// 4. Header order list should include every default header key.
|
|
196
|
+
const orderSet = new Set(headerOrder.map((h) => h.toLowerCase()));
|
|
197
|
+
const headersKeySet = new Set(Object.keys(headers).map((k) => k.toLowerCase()));
|
|
198
|
+
const missingInOrder = [];
|
|
199
|
+
for (const key of headersKeySet) {
|
|
200
|
+
if (!orderSet.has(key)) {
|
|
201
|
+
missingInOrder.push(key);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (missingInOrder.length > 0) {
|
|
205
|
+
issues.push(`headers not in headerOrder: ${missingInOrder.join(", ")}`);
|
|
206
|
+
}
|
|
207
|
+
return result(id, issues.length === 0, { headerOrder, defaultHeaders: Object.keys(headers) }, "RFC 9110 compliant HTTP/1.1 request", issues.length === 0 ? undefined : issues.join("; "));
|
|
21
208
|
}
|
|
22
209
|
//# sourceMappingURL=rfcTests.js.map
|
package/dist/rfc/rfcTests.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rfcTests.js","sourceRoot":"","sources":["../../src/rfc/rfcTests.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rfcTests.js","sourceRoot":"","sources":["../../src/rfc/rfcTests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,UAAU,EAAE,6BAA6B,EAAmC,MAAM,uBAAuB,CAAC;AAEnH,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,gEAAgE;AAChE,SAAS,KAAK,CAAC,CAAS;IACpB,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,wEAAwE;AACxE,SAAS,eAAe,CAAC,KAAiB;IACtC,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAErC,sEAAsE;IACtE,8DAA8D;IAC9D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAE3E,OAAO;QACH,YAAY,EAAE,OAAO;QACrB,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;QACtC,iBAAiB,EAAE,EAAE;QACrB,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;QAC3C,mBAAmB,EAAE,EAAE;QACvB,MAAM;KACT,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,OAAmB;IAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QACvD,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW;IAC9C,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,qDAAqD;AACrD,MAAM,yBAAyB,GAAG,GAAG,CAAC;AACtC,+FAA+F;AAC/F,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAElC,wEAAwE;AACxE,SAAS,eAAe,CAAC,YAAwB;IAC7C,uEAAuE;IACvE,2CAA2C;IAC3C,oCAAoC;IACpC,sBAAsB;IACtB,iBAAiB;IACjB,iDAAiD;IACjD,0CAA0C;IAC1C,IAAI,YAAY,CAAC,MAAM,GAAG,uBAAuB,EAAE,CAAC;QAChD,OAAO,YAAY,CAAC;IACxB,CAAC;IACD,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,SAAS,KAAK,yBAAyB,EAAE,CAAC;QAC1C,uEAAuE;QACvE,sCAAsC;QACtC,OAAO,YAAY,CAAC;IACxB,CAAC;IACD,OAAO,YAAY,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CACX,EAAc,EACd,IAAa,EACb,MAAe,EACf,QAAiB,EACjB,IAAa;IAEb,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACtG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC5B,EAAE,GAAe,SAAkB,EACnC,SAAS,GAAc,YAAyB;IAEhD,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,SAAS,mBAA4B,CAAC,CAAC;IACrE,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,6BAA6B,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEtE,OAAO,MAAM,CACT,EAAE,EACF,UAAU,CAAC,EAAE,EACb,UAAU,EACV,gCAAgC,EAChC,UAAU,CAAC,EAAE;QACT,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC9F,CAAC;AACN,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAC9B,EAAE,GAAe,WAAoB,EACrC,SAAS,GAAc,YAAyB;IAEhD,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,SAAS,iBAA0B,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAE7C,+BAA+B;IAC/B,MAAM,8BAA8B,GAAG,CAAC,CAAC;IACzC,MAAM,2BAA2B,GAAG,CAAC,CAAC;IACtC,MAAM,sBAAsB,GAAG,CAAC,CAAC;IAEjC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;IACxC,MAAM,MAAM,GAA2D;QACnE;YACI,IAAI,EAAE,wBAAwB;YAC9B,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC,8BAA8B,CAAC,KAAK,QAAQ,CAAC,oBAAoB;SACrF;QACD;YACI,IAAI,EAAE,qBAAqB;YAC3B,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC,2BAA2B,CAAC,KAAK,QAAQ,CAAC,iBAAiB;SAC/E;QACD;YACI,IAAI,EAAE,gBAAgB;YACtB,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC,sBAAsB,CAAC,KAAK,QAAQ,CAAC,YAAY;SACrE;KACJ,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3C,OAAO,MAAM,CACT,EAAE,EACF,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAC5B;QACI,oBAAoB,EAAE,QAAQ,CAAC,oBAAoB;QACnD,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;QAC7C,YAAY,EAAE,QAAQ,CAAC,YAAY;KACtC,EACD,MAAM,CAAC,MAAM,KAAK,CAAC;QACf,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,wBAAwB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvE,CAAC;AACN,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAC9B,EAAE,GAAe,WAAoB,EACrC,SAAS,GAAc,YAAyB;IAEhD,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC;IAC7C,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;IAE9C,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,kEAAkE;IAClE,wEAAwE;IACxE,sEAAsE;IACtE,MAAM,OAAO,GACT,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;QACnD,MAAM,IAAI,OAAO;QACjB,MAAM,IAAI,OAAO,CAAC;IACtB,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAChD,CAAC;IAED,qCAAqC;IACrC,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACtE,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,+BAA+B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,mDAAmD;IACnD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,2BAA2B,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,gEAAgE;IAChE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAChF,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACL,CAAC;IACD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,+BAA+B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,MAAM,CACT,EAAE,EACF,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EACrD,qCAAqC,EACrC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CACtD,CAAC;AACN,CAAC"}
|