@browsercore/testing 0.1.2 → 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.
Files changed (178) hide show
  1. package/README.md +5 -0
  2. package/captures/DIVERGENCE_REPORT.md +325 -0
  3. package/captures/IMPERSIONATION_GAPS_REPORT.md +367 -0
  4. package/captures/README.md +56 -0
  5. package/captures/REVIEW_FINDINGS.md +71 -0
  6. package/captures/_probe/analyze.py +67 -0
  7. package/captures/_probe/capture_bytes.py +127 -0
  8. package/captures/_probe/chrome131_determinism.py +32 -0
  9. package/captures/_probe/diagnose_capture.py +59 -0
  10. package/captures/_probe/golden_analysis.py +91 -0
  11. package/captures/_probe/inspect_golden.py +55 -0
  12. package/captures/_probe/inspect_sv.py +16 -0
  13. package/captures/_probe/make_golden.py +170 -0
  14. package/captures/_probe/output/chrome100.json +219 -0
  15. package/captures/_probe/output/chrome101.json +219 -0
  16. package/captures/_probe/output/chrome104.json +219 -0
  17. package/captures/_probe/output/chrome107.json +220 -0
  18. package/captures/_probe/output/chrome110.json +220 -0
  19. package/captures/_probe/output/chrome116.json +220 -0
  20. package/captures/_probe/output/chrome119.json +219 -0
  21. package/captures/_probe/output/chrome120.json +219 -0
  22. package/captures/_probe/output/chrome123.json +223 -0
  23. package/captures/_probe/output/chrome124.json +224 -0
  24. package/captures/_probe/output/chrome131.json +224 -0
  25. package/captures/_probe/output/chrome131_fresh.json +224 -0
  26. package/captures/_probe/output/chrome133a.json +224 -0
  27. package/captures/_probe/output/chrome136.json +224 -0
  28. package/captures/_probe/output/chrome99.json +219 -0
  29. package/captures/_probe/output/edge101.json +219 -0
  30. package/captures/_probe/output/edge99.json +219 -0
  31. package/captures/_probe/output/firefox133.json +227 -0
  32. package/captures/_probe/output/firefox135.json +230 -0
  33. package/captures/_probe/output/safari155.json +210 -0
  34. package/captures/_probe/output/safari170.json +214 -0
  35. package/captures/_probe/output/safari170_fresh.json +214 -0
  36. package/captures/_probe/output/safari180.json +216 -0
  37. package/captures/_probe/output/safari180_ios.json +216 -0
  38. package/captures/_probe/output/safari184.json +215 -0
  39. package/captures/_probe/output/safari260.json +211 -0
  40. package/captures/_probe/output/summary.json +2520 -0
  41. package/captures/_probe/parse_existing_settings.py +15 -0
  42. package/captures/_probe/peetprint.py +39 -0
  43. package/captures/_probe/probe.py +137 -0
  44. package/captures/_probe/raw_bytes/chrome-131_client_hello.bin +0 -0
  45. package/captures/_probe/raw_bytes/chrome-131_client_hello.hex +32 -0
  46. package/captures/_probe/raw_bytes/firefox-133_client_hello.bin +0 -0
  47. package/captures/_probe/raw_bytes/firefox-133_client_hello.hex +32 -0
  48. package/captures/_probe/raw_bytes/safari-17_client_hello.bin +0 -0
  49. package/captures/_probe/raw_bytes/safari-17_client_hello.hex +32 -0
  50. package/captures/_probe/relay_capture.py +137 -0
  51. package/captures/_probe/socks_capture.py +138 -0
  52. package/captures/_probe/validate_fresh.py +66 -0
  53. package/captures/_probe/validate_golden.py +97 -0
  54. package/captures/_probe/verify_capture.py +68 -0
  55. package/captures/chrome-131/http2/settings.bin +0 -0
  56. package/captures/chrome-131/http2/settings.meta.json +9 -0
  57. package/captures/chrome-131/oracle_capture.reference.json +224 -0
  58. package/captures/chrome-131/tls/client_hello.bin +0 -0
  59. package/captures/chrome-131/tls/client_hello.meta.json +20 -0
  60. package/captures/chrome-140/http2/settings.bin +0 -0
  61. package/captures/chrome-140/http2/settings.meta.json +9 -0
  62. package/captures/chrome-140/tls/client_hello.bin +0 -0
  63. package/captures/chrome-140/tls/client_hello.meta.json +12 -0
  64. package/captures/firefox-128/tls/client_hello.bin +0 -0
  65. package/captures/firefox-128/tls/client_hello.meta.json +12 -0
  66. package/captures/firefox-133/http2/settings.bin +0 -0
  67. package/captures/firefox-133/http2/settings.meta.json +9 -0
  68. package/captures/firefox-133/oracle_capture.reference.json +227 -0
  69. package/captures/firefox-133/tls/client_hello.bin +0 -0
  70. package/captures/firefox-133/tls/client_hello.meta.json +20 -0
  71. package/captures/safari-17/http2/settings.bin +0 -0
  72. package/captures/safari-17/http2/settings.meta.json +9 -0
  73. package/captures/safari-17/oracle_capture.reference.json +214 -0
  74. package/captures/safari-17/tls/client_hello.bin +0 -0
  75. package/captures/safari-17/tls/client_hello.meta.json +20 -0
  76. package/dist/bench/bench.d.ts +22 -6
  77. package/dist/bench/bench.d.ts.map +1 -1
  78. package/dist/bench/bench.js +105 -10
  79. package/dist/bench/bench.js.map +1 -1
  80. package/dist/captures/manifest.d.ts.map +1 -1
  81. package/dist/captures/manifest.js +2 -3
  82. package/dist/captures/manifest.js.map +1 -1
  83. package/dist/categories/compression.d.ts +5 -2
  84. package/dist/categories/compression.d.ts.map +1 -1
  85. package/dist/categories/compression.js +54 -6
  86. package/dist/categories/compression.js.map +1 -1
  87. package/dist/categories/cookies.d.ts +3 -2
  88. package/dist/categories/cookies.d.ts.map +1 -1
  89. package/dist/categories/cookies.js +228 -11
  90. package/dist/categories/cookies.js.map +1 -1
  91. package/dist/categories/redirects.d.ts +8 -2
  92. package/dist/categories/redirects.d.ts.map +1 -1
  93. package/dist/categories/redirects.js +97 -12
  94. package/dist/categories/redirects.js.map +1 -1
  95. package/dist/e2e/cert-gen.d.ts +30 -0
  96. package/dist/e2e/cert-gen.d.ts.map +1 -0
  97. package/dist/e2e/cert-gen.js +158 -0
  98. package/dist/e2e/cert-gen.js.map +1 -0
  99. package/dist/e2e/parse-clienthello.d.ts +177 -0
  100. package/dist/e2e/parse-clienthello.d.ts.map +1 -0
  101. package/dist/e2e/parse-clienthello.js +343 -0
  102. package/dist/e2e/parse-clienthello.js.map +1 -0
  103. package/dist/e2e/sink-server.d.ts +109 -0
  104. package/dist/e2e/sink-server.d.ts.map +1 -0
  105. package/dist/e2e/sink-server.js +292 -0
  106. package/dist/e2e/sink-server.js.map +1 -0
  107. package/dist/errors.d.ts.map +1 -1
  108. package/dist/errors.js.map +1 -1
  109. package/dist/fingerprint/index.d.ts +5 -2
  110. package/dist/fingerprint/index.d.ts.map +1 -1
  111. package/dist/fingerprint/index.js +3 -2
  112. package/dist/fingerprint/index.js.map +1 -1
  113. package/dist/fingerprint/ja3.d.ts.map +1 -1
  114. package/dist/fingerprint/ja3.js +33 -4
  115. package/dist/fingerprint/ja3.js.map +1 -1
  116. package/dist/fingerprint/ja4-errors.d.ts +17 -0
  117. package/dist/fingerprint/ja4-errors.d.ts.map +1 -0
  118. package/dist/fingerprint/ja4-errors.js +20 -0
  119. package/dist/fingerprint/ja4-errors.js.map +1 -0
  120. package/dist/fingerprint/ja4-reader.d.ts +44 -0
  121. package/dist/fingerprint/ja4-reader.d.ts.map +1 -0
  122. package/dist/fingerprint/ja4-reader.js +112 -0
  123. package/dist/fingerprint/ja4-reader.js.map +1 -0
  124. package/dist/fingerprint/ja4.d.ts +11 -43
  125. package/dist/fingerprint/ja4.d.ts.map +1 -1
  126. package/dist/fingerprint/ja4.js +54 -141
  127. package/dist/fingerprint/ja4.js.map +1 -1
  128. package/dist/fingerprint/ja4h.d.ts +46 -0
  129. package/dist/fingerprint/ja4h.d.ts.map +1 -0
  130. package/dist/fingerprint/ja4h.js +50 -0
  131. package/dist/fingerprint/ja4h.js.map +1 -0
  132. package/dist/golden/golden.d.ts +15 -1
  133. package/dist/golden/golden.d.ts.map +1 -1
  134. package/dist/golden/golden.js +35 -22
  135. package/dist/golden/golden.js.map +1 -1
  136. package/dist/index.d.ts +17 -1
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +26 -1
  139. package/dist/index.js.map +1 -1
  140. package/dist/reference/browser-provider.d.ts +33 -0
  141. package/dist/reference/browser-provider.d.ts.map +1 -0
  142. package/dist/reference/browser-provider.js +60 -0
  143. package/dist/reference/browser-provider.js.map +1 -0
  144. package/dist/reference/curl-provider.d.ts +40 -0
  145. package/dist/reference/curl-provider.d.ts.map +1 -0
  146. package/dist/reference/curl-provider.js +96 -0
  147. package/dist/reference/curl-provider.js.map +1 -0
  148. package/dist/reference/dump.d.ts +42 -0
  149. package/dist/reference/dump.d.ts.map +1 -0
  150. package/dist/reference/dump.js +102 -0
  151. package/dist/reference/dump.js.map +1 -0
  152. package/dist/reference/node-reference.d.ts.map +1 -1
  153. package/dist/reference/node-reference.js +2 -5
  154. package/dist/reference/node-reference.js.map +1 -1
  155. package/dist/reference/reference-errors.d.ts +36 -0
  156. package/dist/reference/reference-errors.d.ts.map +1 -0
  157. package/dist/reference/reference-errors.js +43 -0
  158. package/dist/reference/reference-errors.js.map +1 -0
  159. package/dist/reference/reference-types.d.ts +33 -0
  160. package/dist/reference/reference-types.d.ts.map +1 -0
  161. package/dist/reference/reference-types.js +9 -0
  162. package/dist/reference/reference-types.js.map +1 -0
  163. package/dist/reference/reference.d.ts +14 -77
  164. package/dist/reference/reference.d.ts.map +1 -1
  165. package/dist/reference/reference.js +43 -207
  166. package/dist/reference/reference.js.map +1 -1
  167. package/dist/rfc/rfcTests.d.ts +46 -8
  168. package/dist/rfc/rfcTests.d.ts.map +1 -1
  169. package/dist/rfc/rfcTests.js +203 -16
  170. package/dist/rfc/rfcTests.js.map +1 -1
  171. package/dist/types.d.ts +2 -1
  172. package/dist/types.d.ts.map +1 -1
  173. package/dist/types.js.map +1 -1
  174. package/dist/utils.d.ts +3 -0
  175. package/dist/utils.d.ts.map +1 -1
  176. package/dist/utils.js +11 -5
  177. package/dist/utils.js.map +1 -1
  178. package/package.json +61 -50
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Minimal TLS 1.2+ ClientHello parser for the E2E TLS sink server.
3
+ *
4
+ * Parses the wire bytes a sink captured *before* TLS parsing, exposing the
5
+ * fields the e2e test matrix (T2–T9 in the plan) asserts on: cipher suites,
6
+ * extension types / order, ALPN protocols, SNI hostname, supported_versions.
7
+ *
8
+ * The parser is intentionally lenient about trailing data — it reads only the
9
+ * ClientHello body and stops. It does NOT validate cryptographic integrity;
10
+ * that is the handshake's job. All offsets are computed dynamically from the
11
+ * parsed structure (never hardcoded) so the same code works whether the input
12
+ * is a bare handshake message or a full TLS record (the caller strips the
13
+ * 5-byte record header via {@link parseClientHello}).
14
+ *
15
+ * This module merges the original minimal parser (shipped on `main`) with the
16
+ * E2E TLS sink server infrastructure (from `feat/e2e-sink`): the `EXT` constant
17
+ * table, `ClientHelloParseError`, richer offset/length metadata on
18
+ * {@link ParsedClientHello}, and GREASE utilities from the sink branch are
19
+ * retained alongside the `decodeSni`, `decodeAlpn`, `decodeSupportedVersions`
20
+ * accessors from `main`.
21
+ */
22
+ // --- Extension-type constants (IANA) ------------------------------------
23
+ export const EXT = {
24
+ SERVER_NAME: 0x0000,
25
+ SUPPORTED_VERSIONS: 0x002b,
26
+ APPLICATION_LAYER_PROTOCOL_NEGOTIATION: 0x0010,
27
+ KEY_SHARE: 0x0033,
28
+ SUPPORTED_GROUPS: 0x000a,
29
+ PSK_KEY_EXCHANGE_MODES: 0x002d,
30
+ SIGNATURE_ALGORITHMS: 0x000d,
31
+ };
32
+ // --- Errors -------------------------------------------------------------
33
+ /** Thrown when the input is too short to be a valid ClientHello. */
34
+ export class ClientHelloParseError extends Error {
35
+ kind = "ClientHelloParseError";
36
+ byteOffset;
37
+ cause;
38
+ constructor(message, byteOffset, options) {
39
+ super(message, options);
40
+ this.name = "ClientHelloParseError";
41
+ this.byteOffset = byteOffset;
42
+ this.cause = options?.cause;
43
+ }
44
+ }
45
+ // --- Parser helpers -----------------------------------------------------
46
+ /** Read a big-endian uint16 at `offset`. */
47
+ function readUint16(buf, offset) {
48
+ return (buf[offset] << 8) | buf[offset + 1];
49
+ }
50
+ /**
51
+ * Parse a TLS record header. Returns the offset of the handshake message that
52
+ * follows (i.e. 5 for a well-formed record) or `null` if the input does not
53
+ * start with a Handshake record (content type 0x16).
54
+ *
55
+ * Callers that already stripped the record header can pass the raw handshake
56
+ * message directly to {@link parseClientHello}.
57
+ */
58
+ export function peekRecordHeader(buf) {
59
+ if (buf.length < 5)
60
+ return null;
61
+ const contentType = buf[0];
62
+ if (contentType !== 0x16)
63
+ return null; // Not a Handshake record.
64
+ // Bytes 1-2: record version (0x0303). Bytes 3-4: fragment length.
65
+ return 5;
66
+ }
67
+ /**
68
+ * Parse a ClientHello from raw wire bytes.
69
+ *
70
+ * Accepts either a full TLS Handshake record (5-byte header + handshake
71
+ * message) or a bare handshake message (type + 24-bit length + body). The
72
+ * function auto-detects the record header by content type.
73
+ *
74
+ * Distinct from the JA3 `parseClientHello` in `@browsercore/testing`'s
75
+ * fingerprint module (which returns JA3 segments). This parser returns the
76
+ * full structural breakdown (cipher suites, extensions, SNI, ALPN, etc.) for
77
+ * the e2e test matrix.
78
+ *
79
+ * @param buf Raw bytes as seen on the wire.
80
+ * @throws {ClientHelloParseError} if the input is malformed or truncated.
81
+ */
82
+ export function parseClientHello(buf) {
83
+ let o = 0;
84
+ const recordOffset = peekRecordHeader(buf);
85
+ if (recordOffset !== null) {
86
+ o = recordOffset; // skip 5-byte record header.
87
+ }
88
+ const start = o;
89
+ if (o + 4 > buf.length) {
90
+ throw new ClientHelloParseError(`ClientHello truncated at handshake header (need 4 bytes at offset ${o}, have ${buf.length - o})`, o);
91
+ }
92
+ const handshakeType = buf[o];
93
+ if (handshakeType !== 0x01) {
94
+ throw new ClientHelloParseError(`Expected handshake type 0x01 (ClientHello), got 0x${handshakeType.toString(16).padStart(2, "0")}`, o);
95
+ }
96
+ const handshakeLength = (buf[o + 1] << 16) | (buf[o + 2] << 8) | buf[o + 3];
97
+ o += 4;
98
+ const bodyEnd = o + handshakeLength;
99
+ if (bodyEnd > buf.length) {
100
+ throw new ClientHelloParseError(`ClientHello body truncated (need ${handshakeLength} bytes, have ${buf.length - o})`, o);
101
+ }
102
+ // legacy_version (2) + random (32).
103
+ if (o + 34 > bodyEnd) {
104
+ throw new ClientHelloParseError("ClientHello too short for legacy_version + random", o);
105
+ }
106
+ const legacyVersion = readUint16(buf, o);
107
+ o += 2;
108
+ const random = buf.subarray(o, o + 32);
109
+ o += 32;
110
+ // session_id: length-prefixed.
111
+ const sessionIdLen = buf[o];
112
+ o += 1;
113
+ if (o + sessionIdLen > bodyEnd) {
114
+ throw new ClientHelloParseError("session_id extends past ClientHello body", o);
115
+ }
116
+ const sessionId = buf.subarray(o, o + sessionIdLen);
117
+ o += sessionIdLen;
118
+ // cipher_suites: 2-byte length prefix + (len/2) suites.
119
+ if (o + 2 > bodyEnd) {
120
+ throw new ClientHelloParseError("cipher_suites length truncated", o);
121
+ }
122
+ const csLen = readUint16(buf, o);
123
+ o += 2;
124
+ if (o + csLen > bodyEnd || csLen % 2 !== 0) {
125
+ throw new ClientHelloParseError("cipher_suites body truncated or odd length", o);
126
+ }
127
+ const suites = [];
128
+ for (let i = 0; i < csLen; i += 2) {
129
+ suites.push(readUint16(buf, o + i));
130
+ }
131
+ o += csLen;
132
+ // compression_methods: 1-byte length prefix + methods.
133
+ const compLen = buf[o];
134
+ o += 1;
135
+ if (o + compLen > bodyEnd) {
136
+ throw new ClientHelloParseError("compression_methods truncated", o);
137
+ }
138
+ const methods = [];
139
+ for (let i = 0; i < compLen; i++) {
140
+ methods.push(buf[o + i]);
141
+ }
142
+ o += compLen;
143
+ // extensions: 2-byte length prefix + extension list (may be absent).
144
+ const extensions = [];
145
+ if (o + 2 <= bodyEnd) {
146
+ const extLen = readUint16(buf, o);
147
+ o += 2;
148
+ const extEnd = o + extLen;
149
+ if (extEnd > bodyEnd) {
150
+ throw new ClientHelloParseError("extensions block truncated", o);
151
+ }
152
+ while (o + 4 <= extEnd) {
153
+ const extType = readUint16(buf, o);
154
+ const extLen2 = readUint16(buf, o + 2);
155
+ o += 4;
156
+ if (o + extLen2 > extEnd) {
157
+ throw new ClientHelloParseError(`extension 0x${extType.toString(16).padStart(4, "0")} data truncated`, o);
158
+ }
159
+ extensions.push({ type: extType, length: extLen2, data: buf.subarray(o, o + extLen2) });
160
+ o += extLen2;
161
+ }
162
+ o = extEnd;
163
+ }
164
+ void o; // body fully consumed; trailing bytes (next record) ignored.
165
+ return {
166
+ handshakeTypeOffset: start,
167
+ handshakeType,
168
+ handshakeLength,
169
+ legacyVersion,
170
+ random,
171
+ sessionId,
172
+ cipherSuites: suites,
173
+ compressionMethods: methods,
174
+ extensions,
175
+ totalLength: bodyEnd - start,
176
+ };
177
+ }
178
+ /**
179
+ * Wire-compatible alias of {@link parseClientHello}. Exported for callers that
180
+ * prefer the `parseClientHelloWire` name used by the E2E TLS sink server.
181
+ */
182
+ export const parseClientHelloWire = parseClientHello;
183
+ // --- Extension accessors ------------------------------------------------
184
+ /**
185
+ * Find the first extension of the given type. Returns `undefined` if absent.
186
+ */
187
+ export function findExtension(hello, type) {
188
+ return hello.extensions.find((e) => e.type === type);
189
+ }
190
+ // --- parse* accessors (feat/e2e-sink naming) ----------------------------
191
+ /**
192
+ * Parse the SNI extension (type 0x0000) and return the first hostname.
193
+ *
194
+ * Layout: server_name_list_len(2) + entries. Each entry: name_type(1) +
195
+ * name_len(2) + name. name_type 0 = host_name.
196
+ */
197
+ export function parseSniHostname(hello) {
198
+ const ext = findExtension(hello, EXT.SERVER_NAME);
199
+ if (ext === undefined || ext.data.length < 3)
200
+ return null;
201
+ let o = 0;
202
+ const listLen = readUint16(ext.data, o);
203
+ o += 2;
204
+ void listLen;
205
+ while (o + 3 <= ext.data.length) {
206
+ const nameType = ext.data[o];
207
+ const nameLen = readUint16(ext.data, o + 1);
208
+ o += 3;
209
+ if (o + nameLen > ext.data.length)
210
+ return null;
211
+ if (nameType === 0) {
212
+ return new TextDecoder().decode(ext.data.subarray(o, o + nameLen));
213
+ }
214
+ o += nameLen;
215
+ }
216
+ return null;
217
+ }
218
+ /**
219
+ * Parse the ALPN extension (type 0x0010) and return the offered protocols in
220
+ * wire order.
221
+ *
222
+ * Layout: protocol_list_len(2) + entries. Each entry: name_len(1) + name.
223
+ */
224
+ export function parseAlpnProtocols(hello) {
225
+ const ext = findExtension(hello, EXT.APPLICATION_LAYER_PROTOCOL_NEGOTIATION);
226
+ if (ext === undefined || ext.data.length < 1)
227
+ return [];
228
+ let o = 0;
229
+ const listLen = readUint16(ext.data, o);
230
+ o += 2;
231
+ void listLen;
232
+ const protocols = [];
233
+ while (o + 1 <= ext.data.length) {
234
+ const nameLen = ext.data[o];
235
+ o += 1;
236
+ if (o + nameLen > ext.data.length)
237
+ break;
238
+ protocols.push(new TextDecoder().decode(ext.data.subarray(o, o + nameLen)));
239
+ o += nameLen;
240
+ }
241
+ return protocols;
242
+ }
243
+ /**
244
+ * Parse the supported_versions extension (type 0x002b) and return the offered
245
+ * versions in wire order.
246
+ *
247
+ * RFC 8446 §4.2.1.1: the client body is a 1-byte length-prefixed list of
248
+ * uint16 wire versions (`versions<2..254>`). The length byte counts the
249
+ * number of *bytes* of version data (always even), so the number of versions
250
+ * is `length / 2`.
251
+ */
252
+ export function parseSupportedVersions(hello) {
253
+ const ext = findExtension(hello, EXT.SUPPORTED_VERSIONS);
254
+ if (ext === undefined || ext.data.length < 1)
255
+ return [];
256
+ const versionsLen = ext.data[0];
257
+ const versions = [];
258
+ let o = 1;
259
+ while (o + 2 <= 1 + versionsLen && o + 2 <= ext.data.length) {
260
+ versions.push(readUint16(ext.data, o));
261
+ o += 2;
262
+ }
263
+ return versions;
264
+ }
265
+ // --- decode* accessors (main branch naming) -----------------------------
266
+ /**
267
+ * Decode the SNI server_name_list (extension 0x0000) into hostnames.
268
+ *
269
+ * RFC 6066 §3: server_name_list = length(2) || entries, each entry =
270
+ * name_type(1) || length(2) || name. We only handle name_type=0 (host_name).
271
+ *
272
+ * Returns all hostnames in the list. Use {@link parseSniHostname} for the
273
+ * first one only.
274
+ */
275
+ export function decodeSni(hello) {
276
+ const ext = findExtension(hello, 0x0000);
277
+ if (ext === undefined || ext.data.length < 2) {
278
+ return [];
279
+ }
280
+ const names = [];
281
+ let pos = 0;
282
+ const listLen = readUint16(ext.data, pos);
283
+ pos += 2;
284
+ const end = pos + listLen;
285
+ while (pos + 3 <= end) {
286
+ const nameType = ext.data[pos];
287
+ const nameLen = readUint16(ext.data, pos + 1);
288
+ pos += 3;
289
+ if (pos + nameLen > end) {
290
+ break;
291
+ }
292
+ const nameBytes = ext.data.subarray(pos, pos + nameLen);
293
+ pos += nameLen;
294
+ if (nameType === 0) {
295
+ names.push(new TextDecoder().decode(nameBytes));
296
+ }
297
+ }
298
+ return names;
299
+ }
300
+ /**
301
+ * Decode the supported_versions extension (0x002b) for a client ClientHello.
302
+ *
303
+ * RFC 8446 §4.2.1: the client body is a length-prefixed list of uint16 wire
304
+ * versions. Returns the versions in wire order.
305
+ *
306
+ * Compatible alias of {@link parseSupportedVersions}.
307
+ */
308
+ export function decodeSupportedVersions(hello) {
309
+ return parseSupportedVersions(hello);
310
+ }
311
+ /**
312
+ * Decode the ALPN extension (0x0010) into the offered protocol names.
313
+ *
314
+ * RFC 7301: the body is a length-prefixed list of length-prefixed UTF-8 names.
315
+ *
316
+ * Compatible alias of {@link parseAlpnProtocols}.
317
+ */
318
+ export function decodeAlpn(hello) {
319
+ return parseAlpnProtocols(hello);
320
+ }
321
+ // --- GREASE sentinel detection ------------------------------------------
322
+ /**
323
+ * The 16 GREASE sentinel values per RFC 8701: both bytes identical, low nibble
324
+ * 0xA. Used to detect / strip GREASE from cipher + extension assertions.
325
+ */
326
+ export const GREASE_SENTINELS = [
327
+ 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a,
328
+ 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa,
329
+ ];
330
+ const GREASE_SET = new Set(GREASE_SENTINELS);
331
+ /** True if the given 16-bit wire value is a GREASE sentinel. */
332
+ export function isGrease(value) {
333
+ return GREASE_SET.has(value);
334
+ }
335
+ /** Return the cipher suites with GREASE sentinels removed. */
336
+ export function nonGreaseCipherSuites(hello) {
337
+ return hello.cipherSuites.filter((s) => !isGrease(s));
338
+ }
339
+ /** Return the extension types with GREASE sentinels removed. */
340
+ export function nonGreaseExtensionTypes(hello) {
341
+ return hello.extensions.filter((e) => !isGrease(e.type)).map((e) => e.type);
342
+ }
343
+ //# sourceMappingURL=parse-clienthello.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-clienthello.js","sourceRoot":"","sources":["../../src/e2e/parse-clienthello.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA8CH,2EAA2E;AAE3E,MAAM,CAAC,MAAM,GAAG,GAAG;IACf,WAAW,EAAE,MAAM;IACnB,kBAAkB,EAAE,MAAM;IAC1B,sCAAsC,EAAE,MAAM;IAC9C,SAAS,EAAE,MAAM;IACjB,gBAAgB,EAAE,MAAM;IACxB,sBAAsB,EAAE,MAAM;IAC9B,oBAAoB,EAAE,MAAM;CACtB,CAAC;AAGX,2EAA2E;AAE3E,oEAAoE;AACpE,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC5B,IAAI,GAAG,uBAAgC,CAAC;IACxC,UAAU,CAAS;IACV,KAAK,CAAoB;IAElD,YAAY,OAAe,EAAE,UAAkB,EAAE,OAA2B;QACxE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,CAAC;IAChC,CAAC;CACJ;AAED,2EAA2E;AAE3E,4CAA4C;AAC5C,SAAS,UAAU,CAAC,GAAe,EAAE,MAAc;IAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAe;IAC5C,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,WAAW,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;IAC5B,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,0BAA0B;IACjE,kEAAkE;IAClE,OAAO,CAAC,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAe;IAC5C,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,YAAY,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QACxB,CAAC,GAAG,YAAY,CAAC,CAAC,6BAA6B;IACnD,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,CAAC;IAChB,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACrB,MAAM,IAAI,qBAAqB,CAC3B,qEAAqE,CAAC,UAAU,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,EACjG,CAAC,CACJ,CAAC;IACN,CAAC;IAED,MAAM,aAAa,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;IAC9B,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,qBAAqB,CAC3B,qDAAqD,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAClG,CAAC,CACJ,CAAC;IACN,CAAC;IACD,MAAM,eAAe,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;IAC/E,CAAC,IAAI,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,CAAC,GAAG,eAAe,CAAC;IACpC,IAAI,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,qBAAqB,CAC3B,oCAAoC,eAAe,gBAAgB,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,EACpF,CAAC,CACJ,CAAC;IACN,CAAC;IAED,oCAAoC;IACpC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;QACnB,MAAM,IAAI,qBAAqB,CAAC,mDAAmD,EAAE,CAAC,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC,IAAI,CAAC,CAAC;IACP,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IACvC,CAAC,IAAI,EAAE,CAAC;IAER,+BAA+B;IAC/B,MAAM,YAAY,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;IAC7B,CAAC,IAAI,CAAC,CAAC;IACP,IAAI,CAAC,GAAG,YAAY,GAAG,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,qBAAqB,CAAC,0CAA0C,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC;IACpD,CAAC,IAAI,YAAY,CAAC;IAElB,wDAAwD;IACxD,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAAC,gCAAgC,EAAE,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACjC,CAAC,IAAI,CAAC,CAAC;IACP,IAAI,CAAC,GAAG,KAAK,GAAG,OAAO,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,qBAAqB,CAAC,4CAA4C,EAAE,CAAC,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,CAAC,IAAI,KAAK,CAAC;IAEX,uDAAuD;IACvD,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;IACxB,CAAC,IAAI,CAAC,CAAC;IACP,IAAI,CAAC,GAAG,OAAO,GAAG,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,qBAAqB,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;IAC9B,CAAC;IACD,CAAC,IAAI,OAAO,CAAC;IAEb,qEAAqE;IACrE,MAAM,UAAU,GAAsB,EAAE,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAClC,CAAC,IAAI,CAAC,CAAC;QACP,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC;QAC1B,IAAI,MAAM,GAAG,OAAO,EAAE,CAAC;YACnB,MAAM,IAAI,qBAAqB,CAAC,4BAA4B,EAAE,CAAC,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACnC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACvC,CAAC,IAAI,CAAC,CAAC;YACP,IAAI,CAAC,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC;gBACvB,MAAM,IAAI,qBAAqB,CAC3B,eAAe,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,iBAAiB,EACrE,CAAC,CACJ,CAAC;YACN,CAAC;YACD,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC,IAAI,OAAO,CAAC;QACjB,CAAC;QACD,CAAC,GAAG,MAAM,CAAC;IACf,CAAC;IAED,KAAK,CAAC,CAAC,CAAC,6DAA6D;IAErE,OAAO;QACH,mBAAmB,EAAE,KAAK;QAC1B,aAAa;QACb,eAAe;QACf,aAAa;QACb,MAAM;QACN,SAAS;QACT,YAAY,EAAE,MAAM;QACpB,kBAAkB,EAAE,OAAO;QAC3B,UAAU;QACV,WAAW,EAAE,OAAO,GAAG,KAAK;KAC/B,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAErD,2EAA2E;AAE3E;;GAEG;AACH,MAAM,UAAU,aAAa,CACzB,KAAwB,EACxB,IAAY;IAEZ,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACzD,CAAC;AAED,2EAA2E;AAE3E;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACrD,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAClD,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC,IAAI,CAAC,CAAC;IACP,KAAK,OAAO,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC,IAAI,CAAC,CAAC;QACP,IAAI,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAC/C,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACjB,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;QACvE,CAAC;QACD,CAAC,IAAI,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACvD,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,sCAAsC,CAAC,CAAC;IAC7E,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACxD,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC,IAAI,CAAC,CAAC;IACP,KAAK,OAAO,CAAC;IACb,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QAC7B,CAAC,IAAI,CAAC,CAAC;QACP,IAAI,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM;QACzC,SAAS,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC5E,CAAC,IAAI,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAwB;IAC3D,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACzD,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACxD,MAAM,WAAW,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;IACjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACvC,CAAC,IAAI,CAAC,CAAC;IACX,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,2EAA2E;AAE3E;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,KAAwB;IAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1C,GAAG,IAAI,CAAC,CAAC;IACT,MAAM,GAAG,GAAG,GAAG,GAAG,OAAO,CAAC;IAC1B,OAAO,GAAG,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAE,CAAC;QAChC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAC9C,GAAG,IAAI,CAAC,CAAC;QACT,IAAI,GAAG,GAAG,OAAO,GAAG,GAAG,EAAE,CAAC;YACtB,MAAM;QACV,CAAC;QACD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QACxD,GAAG,IAAI,OAAO,CAAC;QACf,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACjB,KAAK,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QACpD,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAwB;IAC5D,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAwB;IAC/C,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,2EAA2E;AAE3E;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAsB;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;AAEF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS,gBAAgB,CAAC,CAAC;AAErD,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,KAAa;IAClC,OAAO,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,qBAAqB,CAAC,KAAwB;IAC1D,OAAO,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CAAC,KAAwB;IAC5D,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAChF,CAAC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * E2E TLS sink server for live handshake verification.
3
+ *
4
+ * Performs a **real** TLS 1.3 handshake against the `@browsercore/tls` client
5
+ * stack and returns the raw ClientHello bytes it saw on the wire — captured
6
+ * *before* Node's `tls` module parses them. This is the missing test oracle:
7
+ * every other TLS test in the stack runs against an in-process fixture; the
8
+ * sink exercises the full stack end-to-end over a real TCP socket.
9
+ *
10
+ * ## Two-socket tap
11
+ *
12
+ * Node's `tls` module parses the ClientHello before user code can see it, so
13
+ * the sink uses a two-socket tap (plan §2.3, Approach A):
14
+ *
15
+ * 1. A raw `node:net` server accepts the connection and pauses the socket.
16
+ * 2. It buffers the first bytes (the ClientHello TLS record) by reading until
17
+ * the record length is satisfied.
18
+ * 3. It unshifts the buffer back onto the socket and wraps the socket in a
19
+ * `tls.TLSSocket` (server-side) with a freshly generated self-signed
20
+ * ECDSA P-256 cert.
21
+ * 4. The handshake completes; the captured bytes + parsed inspection are
22
+ * returned via {@link TlsSinkServer.waitForHandshake}.
23
+ *
24
+ * The sink is single-shot by design: {@link TlsSinkServer.waitForHandshake}
25
+ * resolves on the first completed handshake. Tests create a fresh sink per
26
+ * case (ephemeral port, `maxConnections: 1`).
27
+ */
28
+ /** Captured ClientHello — raw bytes + wall-clock capture time. */
29
+ export interface ClientHelloCapture {
30
+ /** Full TLS record(s) as seen on the wire (record header + handshake). */
31
+ readonly raw: Uint8Array;
32
+ /** Epoch milliseconds when the capture completed. */
33
+ readonly receivedAt: number;
34
+ }
35
+ /** Result of a completed handshake against the sink. */
36
+ export interface HandshakeResult {
37
+ /** The raw ClientHello the sink captured before TLS parsing. */
38
+ readonly clientHello: ClientHelloCapture;
39
+ /** Negotiated protocol version, e.g. "TLSv1.3". */
40
+ readonly protocolVersion: string;
41
+ /** Negotiated cipher suite, e.g. "TLS_AES_128_GCM_SHA256". */
42
+ readonly cipherSuite: string;
43
+ /** ALPN protocol negotiated, or null if none. */
44
+ readonly alpnProtocol: string | null;
45
+ /** SNI hostname the client sent, or null if omitted. */
46
+ readonly serverName: string | null;
47
+ /** Wall-clock handshake duration in milliseconds. */
48
+ readonly handshakeDurationMs: number;
49
+ }
50
+ /**
51
+ * Lightweight TLS 1.3 sink server.
52
+ *
53
+ * Not a general-purpose TLS server — it exists only to confirm a real
54
+ * handshake completes and to return the wire bytes it saw. All assertions
55
+ * live in the *test*; the sink just reports.
56
+ */
57
+ export declare class TlsSinkServer {
58
+ private readonly server;
59
+ private readonly ctx;
60
+ private state;
61
+ private waiters;
62
+ private failers;
63
+ /** Host the sink listens on (always 127.0.0.1). */
64
+ readonly host: string;
65
+ /** Ephemeral port the sink listens on. */
66
+ readonly port: number;
67
+ /** ALPN protocols the sink negotiates (empty = no ALPN negotiation). */
68
+ private readonly alpnProtocols;
69
+ private constructor();
70
+ /**
71
+ * Start listening on an ephemeral port on 127.0.0.1.
72
+ *
73
+ * The returned sink is ready to accept one connection. The self-signed
74
+ * cert is generated at call time (throwaway — valid for 1 day).
75
+ *
76
+ * @param opts Optional configuration — ALPN protocols the sink should
77
+ * negotiate (chooses the first client-offered protocol that matches).
78
+ */
79
+ static start(opts?: {
80
+ readonly alpnProtocols?: readonly string[];
81
+ }): Promise<TlsSinkServer>;
82
+ /** Wire up the server's connection handler. Must be called once. */
83
+ private attach;
84
+ /**
85
+ * Handle an incoming TCP connection: buffer the ClientHello, then hand the
86
+ * socket to a server-side TLSSocket for a real handshake.
87
+ */
88
+ private onConnection;
89
+ /**
90
+ * We have the raw ClientHello. Unshift it back onto the socket and wrap
91
+ * the socket in a server-side TLSSocket to complete the handshake.
92
+ */
93
+ private onClientHello;
94
+ /** Transition to the done state and resolve any waiters. */
95
+ private done;
96
+ /** Transition to the failed state and reject any waiters. */
97
+ private fail;
98
+ /**
99
+ * Resolve once a ClientHello has been captured and the handshake completed.
100
+ *
101
+ * @param timeoutMs Max wait in milliseconds (default 5000). The sink is
102
+ * single-shot — only the first handshake resolves this promise.
103
+ * @throws {Error} on timeout or handshake failure.
104
+ */
105
+ waitForHandshake(timeoutMs?: number): Promise<HandshakeResult>;
106
+ /** Stop the server. Resolves once the underlying socket is closed. */
107
+ stop(): Promise<void>;
108
+ }
109
+ //# sourceMappingURL=sink-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink-server.d.ts","sourceRoot":"","sources":["../../src/e2e/sink-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,qDAAqD;IACrD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAED,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;IACzC,mDAAmD;IACnD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qDAAqD;IACrD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACxC;AAUD;;;;;;GAMG;AACH,qBAAa,aAAa;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,OAAO,CAAgD;IAC/D,OAAO,CAAC,OAAO,CAAmC;IAElD,mDAAmD;IACnD,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAElD,OAAO,eAYN;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,GAAE;QAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CA2B9F;IAED,oEAAoE;IACpE,OAAO,CAAC,MAAM;IAId;;;OAGG;IACH,OAAO,CAAC,YAAY;IAuDpB;;;OAGG;IACH,OAAO,CAAC,aAAa;IA6CrB,4DAA4D;IAC5D,OAAO,CAAC,IAAI;IASZ,6DAA6D;IAC7D,OAAO,CAAC,IAAI;IASZ;;;;;;OAMG;IACH,gBAAgB,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAqB3D;IAED,sEAAsE;IACtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CASpB;CACJ"}