@jshookmcp/jshook 0.2.8 → 0.3.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 (162) hide show
  1. package/README.md +36 -5
  2. package/README.zh.md +36 -5
  3. package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-CqGDXmfc.mjs} +160 -54
  4. package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
  5. package/dist/ConsoleMonitor-DykL3IAw.mjs +2269 -0
  6. package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-ETyy0xyo.mjs} +1 -1
  7. package/dist/DetailedDataManager-HT49OrvF.mjs +217 -0
  8. package/dist/EventBus-DFKvADm3.mjs +141 -0
  9. package/dist/EvidenceGraphBridge-318Oi0Lf.mjs +153 -0
  10. package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-BDMsY2Dz.mjs} +27 -13
  11. package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-BN4UQWnX.mjs} +1 -1
  12. package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
  13. package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-DruMgsgj.mjs} +21 -21
  14. package/dist/HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs +566 -0
  15. package/dist/InstrumentationSession-DLH0vd-z.mjs +244 -0
  16. package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CMtviNW_.mjs} +3 -3
  17. package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-ITgb_NMi.mjs} +81 -78
  18. package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-CiL7Z3ey.mjs} +50 -21
  19. package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +20 -56
  20. package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
  21. package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-DMQ44gen.mjs} +16 -16
  22. package/dist/PageController-BPJNqqBN.mjs +431 -0
  23. package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
  24. package/dist/PrerequisiteError-TuyZIs6n.mjs +20 -0
  25. package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
  26. package/dist/ResponseBuilder-CJXWmWNw.mjs +143 -0
  27. package/dist/ReverseEvidenceGraph-C02-gXOh.mjs +269 -0
  28. package/dist/ScriptManager-ZuWD-0Jg.mjs +3003 -0
  29. package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-D-z0umeT.mjs} +2 -2
  30. package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
  31. package/dist/ToolCatalog-5OJdMiF0.mjs +582 -0
  32. package/dist/ToolError-jh9whhMd.mjs +15 -0
  33. package/dist/ToolProbe-DbCFGyrg.mjs +45 -0
  34. package/dist/ToolRegistry-B9krbTtI.mjs +180 -0
  35. package/dist/ToolRouter.policy-BGDAGyeH.mjs +344 -0
  36. package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
  37. package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-C2kjj0ze.mjs} +19 -13
  38. package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-CKrGOTpo.mjs} +3 -3
  39. package/dist/WorkflowEngine-DJ6M4opp.mjs +569 -0
  40. package/dist/analysis-BHeJW2Nb.mjs +1234 -0
  41. package/dist/antidebug-BRKeyt27.mjs +1081 -0
  42. package/dist/artifactRetention-CPXkUJXp.mjs +598 -0
  43. package/dist/artifacts-DkfosXH3.mjs +59 -0
  44. package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
  45. package/dist/betterSqlite3-DLSBZodi.mjs +74 -0
  46. package/dist/binary-instrument--V3MAhJ4.mjs +971 -0
  47. package/dist/bind-helpers-ClV34xdn.mjs +42 -0
  48. package/dist/boringssl-inspector-Bo_LOLaS.mjs +180 -0
  49. package/dist/browser-Dx3_S2cG.mjs +4369 -0
  50. package/dist/capabilities-CcHlvWgK.mjs +33 -0
  51. package/dist/concurrency-Drev_Vz9.mjs +41 -0
  52. package/dist/{constants-CCvsN80K.mjs → constants-CDZLOoVv.mjs} +105 -48
  53. package/dist/coordination-DgItD9DL.mjs +259 -0
  54. package/dist/debugger-RS3RSAqs.mjs +1288 -0
  55. package/dist/definitions-BEoYofW5.mjs +47 -0
  56. package/dist/definitions-BRaefg3u.mjs +365 -0
  57. package/dist/definitions-BbkvZkiv.mjs +96 -0
  58. package/dist/definitions-BtWSHJ3o.mjs +17 -0
  59. package/dist/definitions-C1gCHO0i.mjs +43 -0
  60. package/dist/definitions-CDOg_b-l.mjs +138 -0
  61. package/dist/definitions-CVPD9hzZ.mjs +54 -0
  62. package/dist/definitions-Cea8Lgl7.mjs +94 -0
  63. package/dist/definitions-DAgIyjxM.mjs +10 -0
  64. package/dist/definitions-DJA27nsL.mjs +66 -0
  65. package/dist/definitions-DKPFU3LW.mjs +25 -0
  66. package/dist/definitions-DPRpZQ96.mjs +47 -0
  67. package/dist/definitions-DUE5gmdn.mjs +18 -0
  68. package/dist/definitions-DYVjOtxa.mjs +26 -0
  69. package/dist/definitions-DcYLVLCo.mjs +37 -0
  70. package/dist/definitions-Pp5LI2H4.mjs +27 -0
  71. package/dist/definitions-j9KdHVNR.mjs +14 -0
  72. package/dist/definitions-uzkjBwa7.mjs +258 -0
  73. package/dist/definitions-va-AnLuQ.mjs +28 -0
  74. package/dist/encoding-DJeqHmpd.mjs +1079 -0
  75. package/dist/evidence-graph-bridge-DcYizFk2.mjs +136 -0
  76. package/dist/{factory-CibqTNC8.mjs → factory-C90tBff6.mjs} +41 -56
  77. package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
  78. package/dist/graphql-CoHrhweh.mjs +1197 -0
  79. package/dist/handlers-4jmR0nMs.mjs +898 -0
  80. package/dist/handlers-BAHPxcch.mjs +789 -0
  81. package/dist/handlers-BOs9b907.mjs +2600 -0
  82. package/dist/handlers-BWXEy6ef.mjs +917 -0
  83. package/dist/handlers-Bndn6QvE.mjs +111 -0
  84. package/dist/handlers-BqC4bD4s.mjs +681 -0
  85. package/dist/handlers-BtYq60bM2.mjs +276 -0
  86. package/dist/handlers-BzgcB4iv.mjs +799 -0
  87. package/dist/handlers-CRyRWj2b.mjs +859 -0
  88. package/dist/handlers-CVv2H1uq.mjs +592 -0
  89. package/dist/handlers-Dl5a7JS4.mjs +572 -0
  90. package/dist/handlers-Dx2d7jt7.mjs +2537 -0
  91. package/dist/handlers-Dz9PYsCa.mjs +2805 -0
  92. package/dist/handlers-HujRKC3b.mjs +661 -0
  93. package/dist/handlers.impl-XWXkQfyi.mjs +807 -0
  94. package/dist/hooks-B1B8NRHL.mjs +898 -0
  95. package/dist/index.mjs +491 -259
  96. package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
  97. package/dist/maintenance-PRMkLVRW.mjs +835 -0
  98. package/dist/manifest-67Bok-Si.mjs +58 -0
  99. package/dist/manifest-6lNTMZAB2.mjs +87 -0
  100. package/dist/manifest-B2duEHiH.mjs +90 -0
  101. package/dist/manifest-B6EY9Vm8.mjs +57 -0
  102. package/dist/manifest-B6nKSbyY.mjs +95 -0
  103. package/dist/manifest-BL8AQNPF.mjs +106 -0
  104. package/dist/manifest-BSZvJJmV.mjs +47 -0
  105. package/dist/manifest-BU7qzUyX.mjs +418 -0
  106. package/dist/manifest-Bl62e8WK.mjs +49 -0
  107. package/dist/manifest-Bo5cXjdt.mjs +82 -0
  108. package/dist/manifest-BpS4gtUK.mjs +1347 -0
  109. package/dist/manifest-Bv65_e2W.mjs +101 -0
  110. package/dist/manifest-BytNIF4Z.mjs +117 -0
  111. package/dist/manifest-C-xtsjS3.mjs +81 -0
  112. package/dist/manifest-CDYl7OhA.mjs +66 -0
  113. package/dist/manifest-CRZ3xmkD.mjs +61 -0
  114. package/dist/manifest-CoW6u4Tp.mjs +132 -0
  115. package/dist/manifest-Cq5zN_8A.mjs +50 -0
  116. package/dist/manifest-D7YZM_2e.mjs +194 -0
  117. package/dist/manifest-DE_VrAeQ.mjs +314 -0
  118. package/dist/manifest-DGsXSCpT.mjs +39 -0
  119. package/dist/manifest-DJ2vfEuW.mjs +156 -0
  120. package/dist/manifest-DPXDYhEu.mjs +80 -0
  121. package/dist/manifest-Dd4fQb0a.mjs +322 -0
  122. package/dist/manifest-Deq6opGg.mjs +223 -0
  123. package/dist/manifest-DfJTafJK.mjs +37 -0
  124. package/dist/manifest-DgOdgN_j.mjs +50 -0
  125. package/dist/manifest-DlbMW4v4.mjs +47 -0
  126. package/dist/manifest-DmVfbH0w.mjs +374 -0
  127. package/dist/manifest-Dog6Ddjr.mjs +109 -0
  128. package/dist/manifest-DvgU5FWb.mjs +58 -0
  129. package/dist/manifest-HsfDBs7j.mjs +50 -0
  130. package/dist/manifest-I8oQHvCG.mjs +186 -0
  131. package/dist/manifest-NvH_a-av.mjs +786 -0
  132. package/dist/manifest-cEJU1v0Z.mjs +129 -0
  133. package/dist/manifest-wOl5XLB12.mjs +112 -0
  134. package/dist/modules-tZozf0LQ.mjs +10635 -0
  135. package/dist/mojo-ipc-DXNEXEqb.mjs +640 -0
  136. package/dist/network-CPVvwvFg.mjs +3852 -0
  137. package/dist/{artifacts-BbdOMET5.mjs → outputPaths-um7lCRY3.mjs} +219 -216
  138. package/dist/parse-args-B4cY5Vx5.mjs +39 -0
  139. package/dist/platform-CYeFoTWp.mjs +2161 -0
  140. package/dist/process-BTbgcVc6.mjs +1306 -0
  141. package/dist/proxy-r8YN6nP1.mjs +192 -0
  142. package/dist/registry-Bl8ZQW61.mjs +34 -0
  143. package/dist/response-CWhh2aLo.mjs +34 -0
  144. package/dist/server/plugin-api.mjs +2 -2
  145. package/dist/shared-state-board-BoZnSoj-.mjs +586 -0
  146. package/dist/sourcemap-BIDHUVXy.mjs +934 -0
  147. package/dist/ssrf-policy-Dsqd-DTX.mjs +166 -0
  148. package/dist/streaming-Dal6utPp.mjs +725 -0
  149. package/dist/tool-builder-BHJp32mV.mjs +186 -0
  150. package/dist/transform-DRVgGG90.mjs +1011 -0
  151. package/dist/types-Bx92KJfT.mjs +4 -0
  152. package/dist/wasm-BYx5UOeG.mjs +1044 -0
  153. package/dist/webcrack-Be0_FccV.mjs +747 -0
  154. package/dist/workflow-BpuKEtvn.mjs +725 -0
  155. package/package.json +82 -49
  156. package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
  157. package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
  158. package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
  159. package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
  160. package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
  161. package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-nnMvEohD.mjs} +0 -0
  162. package/dist/{types-BBjOqye-.mjs → types-DDBWs9UP.mjs} +1 -1
@@ -0,0 +1,2537 @@
1
+ import { an as PROTO_HTTP_CONFIDENCE, cn as PROTO_TLS_MIN_RECORD_LEN, ln as PROTO_WS_CONFIDENCE, on as PROTO_SSH_CONFIDENCE, sn as PROTO_TLS_CONFIDENCE } from "./constants-CDZLOoVv.mjs";
2
+ import { n as asJsonResponse } from "./response-CWhh2aLo.mjs";
3
+ import { i as argObject, o as argStringArray, s as argStringRequired } from "./parse-args-B4cY5Vx5.mjs";
4
+ import { readFile as readFile$1, writeFile as writeFile$1 } from "node:fs/promises";
5
+ import { isIP } from "node:net";
6
+ //#region src/server/domains/protocol-analysis/handlers/fingerprint-utils.ts
7
+ const TLS_RECORD_TYPES = {
8
+ 20: "ChangeCipherSpec",
9
+ 21: "Alert",
10
+ 22: "Handshake",
11
+ 23: "ApplicationData"
12
+ };
13
+ const TLS_VERSIONS = {
14
+ "0300": "SSL 3.0",
15
+ "0301": "TLS 1.0",
16
+ "0302": "TLS 1.1",
17
+ "0303": "TLS 1.2",
18
+ "0304": "TLS 1.3"
19
+ };
20
+ const TLS_CIPHER_NAMES = {
21
+ "1301": "TLS_AES_128_GCM_SHA256",
22
+ "1302": "TLS_AES_256_GCM_SHA384",
23
+ "1303": "TLS_CHACHA20_POLY1305_SHA256",
24
+ c02b: "TLS_ECDHE_ECDSA_AES_128_GCM_SHA256",
25
+ c02f: "TLS_ECDHE_RSA_AES_128_GCM_SHA256",
26
+ c02c: "TLS_ECDHE_ECDSA_AES_256_GCM_SHA384",
27
+ c030: "TLS_ECDHE_RSA_AES_256_GCM_SHA384",
28
+ cca9: "TLS_ECDHE_ECDSA_CHACHA20_POLY1305",
29
+ cca8: "TLS_ECDHE_RSA_CHACHA20_POLY1305",
30
+ "009c": "TLS_RSA_AES_128_GCM_SHA256",
31
+ "009d": "TLS_RSA_AES_256_GCM_SHA384",
32
+ "002f": "TLS_RSA_AES_128_CBC_SHA",
33
+ "0035": "TLS_RSA_AES_256_CBC_SHA",
34
+ c013: "TLS_ECDHE_RSA_AES_128_CBC_SHA",
35
+ c014: "TLS_ECDHE_RSA_AES_256_CBC_SHA",
36
+ "00ff": "TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
37
+ "5600": "TLS_FALLBACK_SCSV"
38
+ };
39
+ const DNS_RCODES = {
40
+ 0: "NOERROR",
41
+ 1: "FORMERR",
42
+ 2: "SERVFAIL",
43
+ 3: "NXDOMAIN",
44
+ 4: "NOTIMP",
45
+ 5: "REFUSED"
46
+ };
47
+ const DNS_OPTYPES = {
48
+ 0: "QUERY",
49
+ 1: "IQUERY",
50
+ 2: "STATUS",
51
+ 3: "UNASSIGNED",
52
+ 4: "NOTIFY",
53
+ 5: "UPDATE"
54
+ };
55
+ const HTTP_METHODS = {
56
+ "474554": "GET",
57
+ "504f5354": "POST",
58
+ "505554": "PUT",
59
+ "44454c45": "DELETE",
60
+ "48454144": "HEAD",
61
+ "50415443": "PATCH",
62
+ "4f505449": "OPTIONS",
63
+ "434f4e4e": "CONNECT"
64
+ };
65
+ const TLS_EXTENSION_NAMES = {
66
+ "0000": "server_name",
67
+ "000a": "supported_groups",
68
+ "000b": "ec_point_formats",
69
+ "000d": "signature_algorithms",
70
+ "0010": "application_layer_protocol_negotiation",
71
+ "0015": "padding",
72
+ "0017": "extended_master_secret",
73
+ "001b": "compress_certificate",
74
+ "0023": "session_ticket",
75
+ "0029": "pre_shared_key",
76
+ "002b": "supported_versions",
77
+ "002d": "psk_key_exchange_modes",
78
+ "0033": "key_share",
79
+ "0039": "quic_transport_parameters",
80
+ "4469": "next_protocol_negotiation",
81
+ fe0d: "encrypted_client_hello",
82
+ ff01: "renegotiation_info"
83
+ };
84
+ const WS_OPCODES = {
85
+ 0: "continuation",
86
+ 1: "text",
87
+ 2: "binary",
88
+ 8: "close",
89
+ 9: "ping",
90
+ 10: "pong"
91
+ };
92
+ function readU8(hex, offset) {
93
+ return Number.parseInt(hex.substring(offset * 2, offset * 2 + 2), 16);
94
+ }
95
+ function readU16(hex, offset) {
96
+ return Number.parseInt(hex.substring(offset * 2, offset * 2 + 4), 16);
97
+ }
98
+ function hexSlice(hex, offset, len) {
99
+ return hex.substring(offset * 2, (offset + len) * 2);
100
+ }
101
+ function isZeroedDnsHeader(hex) {
102
+ return hex.length >= 24 && /^0{24}$/i.test(hex.slice(0, 24));
103
+ }
104
+ function parseTlsClientHello(hex) {
105
+ if (hex.length < 44) return null;
106
+ const recordType = readU8(hex, 0);
107
+ if (recordType !== 22) return null;
108
+ const recordVersion = hexSlice(hex, 1, 2);
109
+ const recordLen = readU16(hex, 3);
110
+ if (hex.length / 2 < 5 + recordLen) return null;
111
+ if (readU8(hex, 5) !== 1) return null;
112
+ const result = {
113
+ recordType: TLS_RECORD_TYPES[recordType] ?? `0x${recordType.toString(16)}`,
114
+ recordVersion: TLS_VERSIONS[recordVersion] ?? recordVersion,
115
+ recordLength: recordLen,
116
+ handshakeType: "ClientHello"
117
+ };
118
+ let pos = 9;
119
+ if (pos + 2 > hex.length / 2) return result;
120
+ const clientVersion = hexSlice(hex, pos, 2);
121
+ result.clientVersion = TLS_VERSIONS[clientVersion] ?? clientVersion;
122
+ pos += 34;
123
+ if (pos >= hex.length / 2) return result;
124
+ const sessionIdLen = readU8(hex, pos);
125
+ pos += 1 + sessionIdLen;
126
+ if (pos + 2 > hex.length / 2) return result;
127
+ const cipherLen = readU16(hex, pos);
128
+ pos += 2;
129
+ const ciphers = [];
130
+ for (let i = 0; i < cipherLen / 2 && pos + 2 <= hex.length / 2; i++) {
131
+ const cipherHex = hexSlice(hex, pos, 2).toLowerCase();
132
+ ciphers.push({
133
+ hex: cipherHex,
134
+ name: TLS_CIPHER_NAMES[cipherHex] ?? `Unknown(0x${cipherHex})`
135
+ });
136
+ pos += 2;
137
+ }
138
+ result.cipherSuites = ciphers;
139
+ result.cipherSuiteCount = ciphers.length;
140
+ if (pos >= hex.length / 2) return result;
141
+ const compLen = readU8(hex, pos);
142
+ pos += 1 + compLen;
143
+ if (pos + 2 > hex.length / 2) return result;
144
+ const extTotalLen = readU16(hex, pos);
145
+ pos += 2;
146
+ const extEnd = pos + extTotalLen;
147
+ const extensions = [];
148
+ while (pos + 4 <= extEnd && pos + 4 <= hex.length / 2) {
149
+ const extType = hexSlice(hex, pos, 2).toLowerCase();
150
+ const extLen = readU16(hex, pos + 2);
151
+ extensions.push({
152
+ type: extType,
153
+ length: extLen,
154
+ name: TLS_EXTENSION_NAMES[extType]
155
+ });
156
+ pos += 4 + extLen;
157
+ }
158
+ result.extensions = extensions;
159
+ result.extensionCount = extensions.length;
160
+ return result;
161
+ }
162
+ function parseDnsHeader(hex) {
163
+ if (hex.length < 24) return null;
164
+ const txId = readU16(hex, 0);
165
+ const flags1 = readU8(hex, 2);
166
+ const flags2 = readU8(hex, 3);
167
+ const qr = flags1 >> 7 & 1;
168
+ const opcode = flags1 >> 3 & 15;
169
+ const aa = flags1 >> 2 & 1;
170
+ const tc = flags1 >> 1 & 1;
171
+ const rd = flags1 & 1;
172
+ const ra = flags2 >> 7 & 1;
173
+ const z = flags2 >> 4 & 7;
174
+ const rcode = flags2 & 15;
175
+ return {
176
+ transactionId: `0x${txId.toString(16).padStart(4, "0")}`,
177
+ flags: {
178
+ qr: qr === 1 ? "Response" : "Query",
179
+ opcode: DNS_OPTYPES[opcode] ?? opcode,
180
+ authoritativeAnswer: !!aa,
181
+ truncation: !!tc,
182
+ recursionDesired: !!rd,
183
+ recursionAvailable: !!ra,
184
+ reserved: z,
185
+ responseCode: DNS_RCODES[rcode] ?? rcode
186
+ },
187
+ questionCount: readU16(hex, 4),
188
+ answerCount: readU16(hex, 6),
189
+ authorityCount: readU16(hex, 8),
190
+ additionalCount: readU16(hex, 10)
191
+ };
192
+ }
193
+ function isLikelyDnsHeader(hex) {
194
+ if (hex.length < 24 || isZeroedDnsHeader(hex)) return false;
195
+ const flags1 = readU8(hex, 2);
196
+ const flags2 = readU8(hex, 3);
197
+ const qr = flags1 >> 7 & 1;
198
+ const opcode = flags1 >> 3 & 15;
199
+ const rcode = flags2 & 15;
200
+ const qdcount = readU16(hex, 4);
201
+ const ancount = readU16(hex, 6);
202
+ if (opcode > 2) return false;
203
+ if (qdcount + ancount === 0) return false;
204
+ if (qr === 0 && rcode !== 0) return false;
205
+ if (qr === 1 && rcode > 5) return false;
206
+ return true;
207
+ }
208
+ //#endregion
209
+ //#region src/server/domains/protocol-analysis/handlers/shared/protocol-schema.ts
210
+ function isRecord$1(value) {
211
+ return value !== null && typeof value === "object" && !Array.isArray(value);
212
+ }
213
+ function parseFieldSpec(value, index) {
214
+ if (!isRecord$1(value)) throw new Error(`fields[${index}] must be an object`);
215
+ const name = value.name;
216
+ const offset = value.offset;
217
+ const length = value.length;
218
+ const type = value.type;
219
+ if (typeof name !== "string" || name.trim().length === 0) throw new Error(`fields[${index}].name must be a non-empty string`);
220
+ if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) throw new Error(`fields[${index}].offset must be a non-negative integer`);
221
+ if (typeof length !== "number" || !Number.isInteger(length) || length <= 0) throw new Error(`fields[${index}].length must be a positive integer`);
222
+ if (type !== "int" && type !== "string" && type !== "bytes" && type !== "bool" && type !== "float") throw new Error(`fields[${index}].type is invalid`);
223
+ return {
224
+ name,
225
+ offset,
226
+ length,
227
+ type
228
+ };
229
+ }
230
+ function parseLegacyField(value, index) {
231
+ if (!isRecord$1(value)) throw new Error(`fields[${index}] must be an object`);
232
+ const name = value.name;
233
+ const offset = value.offset;
234
+ const length = value.length;
235
+ const type = value.type;
236
+ const description = value.description;
237
+ if (typeof name !== "string" || name.trim().length === 0) throw new Error(`fields[${index}].name must be a non-empty string`);
238
+ if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) throw new Error(`fields[${index}].offset must be a non-negative integer`);
239
+ if (typeof length !== "number" || !Number.isInteger(length) || length <= 0) throw new Error(`fields[${index}].length must be a positive integer`);
240
+ if (type !== "uint8" && type !== "uint16" && type !== "uint32" && type !== "int64" && type !== "float" && type !== "string" && type !== "bytes") throw new Error(`fields[${index}].type is invalid`);
241
+ return {
242
+ name,
243
+ offset,
244
+ length,
245
+ type,
246
+ ...typeof description === "string" ? { description } : {}
247
+ };
248
+ }
249
+ function parsePatternSpec(name, value) {
250
+ const rawFields = value.fields;
251
+ if (!Array.isArray(rawFields)) throw new Error("spec.fields must be an array");
252
+ const fieldDelimiter = typeof value.fieldDelimiter === "string" && value.fieldDelimiter.length > 0 ? value.fieldDelimiter : void 0;
253
+ const byteOrderValue = value.byteOrder;
254
+ const byteOrder = byteOrderValue === "le" || byteOrderValue === "be" ? byteOrderValue : void 0;
255
+ return {
256
+ name,
257
+ ...fieldDelimiter ? { fieldDelimiter } : {},
258
+ ...byteOrder ? { byteOrder } : {},
259
+ fields: rawFields.map((field, index) => parseFieldSpec(field, index))
260
+ };
261
+ }
262
+ function parseEncryptionInfo(value) {
263
+ if (!isRecord$1(value)) return;
264
+ const type = value.type;
265
+ if (type !== "aes" && type !== "xor" && type !== "rc4" && type !== "custom") return;
266
+ const key = typeof value.key === "string" ? value.key : void 0;
267
+ const iv = typeof value.iv === "string" ? value.iv : void 0;
268
+ const notes = typeof value.notes === "string" ? value.notes : void 0;
269
+ return {
270
+ type,
271
+ ...key ? { key } : {},
272
+ ...iv ? { iv } : {},
273
+ ...notes ? { notes } : {}
274
+ };
275
+ }
276
+ function parseProtocolMessage(value, index) {
277
+ if (!isRecord$1(value)) throw new Error(`messages[${index}] must be an object`);
278
+ const direction = value.direction;
279
+ const timestamp = value.timestamp;
280
+ const fields = value.fields;
281
+ const raw = value.raw;
282
+ if (direction !== "req" && direction !== "res") throw new Error(`messages[${index}].direction must be "req" or "res"`);
283
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) throw new Error(`messages[${index}].timestamp must be a number`);
284
+ if (!isRecord$1(fields)) throw new Error(`messages[${index}].fields must be an object`);
285
+ if (typeof raw !== "string") throw new Error(`messages[${index}].raw must be a string`);
286
+ return {
287
+ direction,
288
+ timestamp,
289
+ fields,
290
+ raw
291
+ };
292
+ }
293
+ //#endregion
294
+ //#region src/server/domains/protocol-analysis/handlers/shared/payload/core.ts
295
+ const TEXT_ENCODINGS = ["utf8", "ascii"];
296
+ const BINARY_ENCODINGS = [
297
+ "utf8",
298
+ "ascii",
299
+ "hex",
300
+ "base64"
301
+ ];
302
+ const PAYLOAD_FIELD_TYPES = [
303
+ "u8",
304
+ "u16",
305
+ "u32",
306
+ "i8",
307
+ "i16",
308
+ "i32",
309
+ "string",
310
+ "bytes"
311
+ ];
312
+ const MUTATION_STRATEGIES = [
313
+ "set_byte",
314
+ "flip_bit",
315
+ "overwrite_bytes",
316
+ "append_bytes",
317
+ "truncate",
318
+ "increment_integer"
319
+ ];
320
+ function parseEndian(value, fallback = "big") {
321
+ return value === "little" ? "little" : fallback;
322
+ }
323
+ function parseNonNegativeInteger(value, label) {
324
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw new Error(`${label} must be a non-negative integer`);
325
+ return value;
326
+ }
327
+ function parsePositiveInteger(value, label) {
328
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
329
+ return value;
330
+ }
331
+ function parseInteger(value, label) {
332
+ if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${label} must be an integer`);
333
+ return value;
334
+ }
335
+ function parseByte(value, label) {
336
+ const parsed = parseInteger(value, label);
337
+ if (parsed < 0 || parsed > 255) throw new Error(`${label} must be between 0 and 255`);
338
+ return parsed;
339
+ }
340
+ function parseOptionalLength(value, label) {
341
+ return value === void 0 ? void 0 : parsePositiveInteger(value, label);
342
+ }
343
+ function parseEncoding(value, allowed, fallback, label) {
344
+ if (value === void 0) return fallback;
345
+ if (typeof value !== "string" || !allowed.includes(value)) throw new Error(`${label} is invalid`);
346
+ return value;
347
+ }
348
+ function expectString(value, label) {
349
+ if (typeof value !== "string") throw new Error(`${label} must be a string`);
350
+ return value;
351
+ }
352
+ function normalizeHexString(value, label) {
353
+ const normalized = value.replace(/^0x/i, "").replace(/\s+/g, "");
354
+ if (normalized.length === 0) return normalized;
355
+ if (normalized.length % 2 !== 0 || /[^0-9a-f]/i.test(normalized)) throw new Error(`${label} must be a valid even-length hex string`);
356
+ return normalized.toLowerCase();
357
+ }
358
+ function decodeBinaryValue(value, encoding, label) {
359
+ switch (encoding) {
360
+ case "utf8":
361
+ case "ascii": return Buffer.from(value, encoding);
362
+ case "hex": return Buffer.from(normalizeHexString(value, label), "hex");
363
+ case "base64": return Buffer.from(value, "base64");
364
+ }
365
+ }
366
+ function getNumericRange(width, signed) {
367
+ const bits = width * 8;
368
+ if (signed) return {
369
+ min: -(2 ** (bits - 1)),
370
+ max: 2 ** (bits - 1) - 1
371
+ };
372
+ return {
373
+ min: 0,
374
+ max: 2 ** bits - 1
375
+ };
376
+ }
377
+ function getFieldNumericMetadata(type) {
378
+ switch (type) {
379
+ case "u8": return {
380
+ width: 1,
381
+ signed: false
382
+ };
383
+ case "u16": return {
384
+ width: 2,
385
+ signed: false
386
+ };
387
+ case "u32": return {
388
+ width: 4,
389
+ signed: false
390
+ };
391
+ case "i8": return {
392
+ width: 1,
393
+ signed: true
394
+ };
395
+ case "i16": return {
396
+ width: 2,
397
+ signed: true
398
+ };
399
+ case "i32": return {
400
+ width: 4,
401
+ signed: true
402
+ };
403
+ default: return null;
404
+ }
405
+ }
406
+ function writeIntegerToBuffer(buffer, value, width, signed, endian) {
407
+ if (signed) switch (width) {
408
+ case 1:
409
+ buffer.writeInt8(value, 0);
410
+ return;
411
+ case 2:
412
+ if (endian === "little") buffer.writeInt16LE(value, 0);
413
+ else buffer.writeInt16BE(value, 0);
414
+ return;
415
+ case 4:
416
+ if (endian === "little") buffer.writeInt32LE(value, 0);
417
+ else buffer.writeInt32BE(value, 0);
418
+ return;
419
+ }
420
+ switch (width) {
421
+ case 1:
422
+ buffer.writeUInt8(value, 0);
423
+ return;
424
+ case 2:
425
+ if (endian === "little") buffer.writeUInt16LE(value, 0);
426
+ else buffer.writeUInt16BE(value, 0);
427
+ return;
428
+ case 4:
429
+ if (endian === "little") buffer.writeUInt32LE(value, 0);
430
+ else buffer.writeUInt32BE(value, 0);
431
+ return;
432
+ }
433
+ }
434
+ function readIntegerFromBuffer(buffer, offset, width, signed, endian) {
435
+ if (signed) switch (width) {
436
+ case 1: return buffer.readInt8(offset);
437
+ case 2: return endian === "little" ? buffer.readInt16LE(offset) : buffer.readInt16BE(offset);
438
+ case 4: return endian === "little" ? buffer.readInt32LE(offset) : buffer.readInt32BE(offset);
439
+ }
440
+ switch (width) {
441
+ case 1: return buffer.readUInt8(offset);
442
+ case 2: return endian === "little" ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
443
+ case 4: return endian === "little" ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
444
+ }
445
+ }
446
+ function applyFixedLength(encoded, length, padByte) {
447
+ if (length === void 0 || encoded.length === length) return encoded;
448
+ if (encoded.length > length) return encoded.subarray(0, length);
449
+ return Buffer.concat([encoded, Buffer.alloc(length - encoded.length, padByte)]);
450
+ }
451
+ //#endregion
452
+ //#region src/server/domains/protocol-analysis/handlers/shared/payload/template.ts
453
+ function parsePayloadTemplateField(value, index) {
454
+ if (!isRecord$1(value)) throw new Error(`fields[${index}] must be an object`);
455
+ const name = value.name;
456
+ const type = value.type;
457
+ const rawValue = value.value;
458
+ if (typeof name !== "string" || name.trim().length === 0) throw new Error(`fields[${index}].name must be a non-empty string`);
459
+ if (typeof type !== "string" || !PAYLOAD_FIELD_TYPES.includes(type)) throw new Error(`fields[${index}].type is invalid`);
460
+ const fieldType = type;
461
+ const numericMetadata = getFieldNumericMetadata(fieldType);
462
+ if (numericMetadata) {
463
+ const numericValue = parseInteger(rawValue, `fields[${index}].value`);
464
+ const range = getNumericRange(numericMetadata.width, numericMetadata.signed);
465
+ if (numericValue < range.min || numericValue > range.max) throw new Error(`fields[${index}].value is out of range for ${type} (${range.min}..${range.max})`);
466
+ if (value.length !== void 0 || value.padByte !== void 0 || value.encoding !== void 0) throw new Error(`fields[${index}] does not support length, padByte, or encoding`);
467
+ return {
468
+ name,
469
+ type: fieldType,
470
+ value: numericValue
471
+ };
472
+ }
473
+ const stringValue = expectString(rawValue, `fields[${index}].value`);
474
+ const length = parseOptionalLength(value.length, `fields[${index}].length`);
475
+ const padByte = value.padByte === void 0 ? 0 : parseByte(value.padByte, `fields[${index}].padByte`);
476
+ if (type === "string") return {
477
+ name,
478
+ type: "string",
479
+ value: stringValue,
480
+ encoding: parseEncoding(value.encoding, TEXT_ENCODINGS, "utf8", `fields[${index}].encoding`),
481
+ ...length !== void 0 ? { length } : {},
482
+ padByte
483
+ };
484
+ return {
485
+ name,
486
+ type: "bytes",
487
+ value: stringValue,
488
+ encoding: parseEncoding(value.encoding, BINARY_ENCODINGS, "hex", `fields[${index}].encoding`),
489
+ ...length !== void 0 ? { length } : {},
490
+ padByte
491
+ };
492
+ }
493
+ function encodePayloadTemplateField(field, endian) {
494
+ switch (field.type) {
495
+ case "u8":
496
+ case "u16":
497
+ case "u32":
498
+ case "i8":
499
+ case "i16":
500
+ case "i32": {
501
+ const numericMetadata = getFieldNumericMetadata(field.type);
502
+ if (!numericMetadata) throw new Error(`Unsupported numeric field type: ${field.type}`);
503
+ const buffer = Buffer.alloc(numericMetadata.width);
504
+ writeIntegerToBuffer(buffer, field.value, numericMetadata.width, numericMetadata.signed, endian);
505
+ return buffer;
506
+ }
507
+ case "string": return applyFixedLength(Buffer.from(field.value, field.encoding), field.length, field.padByte);
508
+ case "bytes": return applyFixedLength(decodeBinaryValue(field.value, field.encoding, `field ${field.name}`), field.length, field.padByte);
509
+ }
510
+ }
511
+ function buildPayloadFromTemplate(fields, endian) {
512
+ const buffers = [];
513
+ const segments = [];
514
+ let offset = 0;
515
+ for (const field of fields) {
516
+ const encoded = encodePayloadTemplateField(field, endian);
517
+ buffers.push(encoded);
518
+ segments.push({
519
+ name: field.name,
520
+ offset,
521
+ length: encoded.length,
522
+ hex: encoded.toString("hex")
523
+ });
524
+ offset += encoded.length;
525
+ }
526
+ return {
527
+ payload: Buffer.concat(buffers),
528
+ segments
529
+ };
530
+ }
531
+ //#endregion
532
+ //#region src/server/domains/protocol-analysis/handlers/shared/payload/mutation.ts
533
+ function parsePayloadMutation(value, index) {
534
+ if (!isRecord$1(value)) throw new Error(`mutations[${index}] must be an object`);
535
+ const strategy = value.strategy;
536
+ if (typeof strategy !== "string" || !MUTATION_STRATEGIES.includes(strategy)) throw new Error(`mutations[${index}].strategy is invalid`);
537
+ switch (strategy) {
538
+ case "set_byte": return {
539
+ strategy: "set_byte",
540
+ offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
541
+ value: parseByte(value.value, `mutations[${index}].value`)
542
+ };
543
+ case "flip_bit": return {
544
+ strategy: "flip_bit",
545
+ offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
546
+ bit: (() => {
547
+ const bit = parseInteger(value.bit, `mutations[${index}].bit`);
548
+ if (bit < 0 || bit > 7) throw new Error(`mutations[${index}].bit must be between 0 and 7`);
549
+ return bit;
550
+ })()
551
+ };
552
+ case "overwrite_bytes": return {
553
+ strategy: "overwrite_bytes",
554
+ offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
555
+ data: decodeBinaryValue(expectString(value.data, `mutations[${index}].data`), parseEncoding(value.encoding, BINARY_ENCODINGS, "hex", `mutations[${index}].encoding`), `mutations[${index}].data`)
556
+ };
557
+ case "append_bytes": return {
558
+ strategy: "append_bytes",
559
+ data: decodeBinaryValue(expectString(value.data, `mutations[${index}].data`), parseEncoding(value.encoding, BINARY_ENCODINGS, "hex", `mutations[${index}].encoding`), `mutations[${index}].data`)
560
+ };
561
+ case "truncate": return {
562
+ strategy: "truncate",
563
+ length: parseNonNegativeInteger(value.length, `mutations[${index}].length`)
564
+ };
565
+ case "increment_integer": {
566
+ const width = value.width;
567
+ if (width !== 1 && width !== 2 && width !== 4) throw new Error(`mutations[${index}].width must be 1, 2, or 4`);
568
+ return {
569
+ strategy: "increment_integer",
570
+ offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
571
+ width,
572
+ delta: parseInteger(value.delta, `mutations[${index}].delta`),
573
+ endian: parseEndian(value.endian),
574
+ signed: value.signed === true
575
+ };
576
+ }
577
+ }
578
+ }
579
+ function applyPayloadMutation(payload, mutation, index) {
580
+ const working = Buffer.from(payload);
581
+ switch (mutation.strategy) {
582
+ case "set_byte":
583
+ if (mutation.offset >= working.length) throw new Error(`mutations[${index}] offset is outside the payload`);
584
+ working[mutation.offset] = mutation.value;
585
+ return {
586
+ payload: working,
587
+ summary: {
588
+ index,
589
+ strategy: mutation.strategy,
590
+ detail: `set payload[${mutation.offset}] to ${mutation.value}`
591
+ }
592
+ };
593
+ case "flip_bit":
594
+ if (mutation.offset >= working.length) throw new Error(`mutations[${index}] offset is outside the payload`);
595
+ {
596
+ const currentByte = working[mutation.offset];
597
+ working[mutation.offset] = currentByte ^ 1 << mutation.bit;
598
+ }
599
+ return {
600
+ payload: working,
601
+ summary: {
602
+ index,
603
+ strategy: mutation.strategy,
604
+ detail: `flipped bit ${mutation.bit} at offset ${mutation.offset}`
605
+ }
606
+ };
607
+ case "overwrite_bytes":
608
+ if (mutation.offset + mutation.data.length > working.length) throw new Error(`mutations[${index}] overwrite exceeds payload length`);
609
+ mutation.data.copy(working, mutation.offset);
610
+ return {
611
+ payload: working,
612
+ summary: {
613
+ index,
614
+ strategy: mutation.strategy,
615
+ detail: `overwrote ${mutation.data.length} bytes at offset ${mutation.offset}`
616
+ }
617
+ };
618
+ case "append_bytes": return {
619
+ payload: Buffer.concat([working, mutation.data]),
620
+ summary: {
621
+ index,
622
+ strategy: mutation.strategy,
623
+ detail: `appended ${mutation.data.length} bytes`
624
+ }
625
+ };
626
+ case "truncate":
627
+ if (mutation.length > working.length) throw new Error(`mutations[${index}] length exceeds payload size`);
628
+ return {
629
+ payload: working.subarray(0, mutation.length),
630
+ summary: {
631
+ index,
632
+ strategy: mutation.strategy,
633
+ detail: `truncated payload to ${mutation.length} bytes`
634
+ }
635
+ };
636
+ case "increment_integer": {
637
+ if (mutation.offset + mutation.width > working.length) throw new Error(`mutations[${index}] integer range exceeds payload length`);
638
+ const next = readIntegerFromBuffer(working, mutation.offset, mutation.width, mutation.signed, mutation.endian) + mutation.delta;
639
+ const range = getNumericRange(mutation.width, mutation.signed);
640
+ if (next < range.min || next > range.max) throw new Error(`mutations[${index}] integer overflow (${range.min}..${range.max})`);
641
+ writeIntegerToBuffer(working.subarray(mutation.offset, mutation.offset + mutation.width), next, mutation.width, mutation.signed, mutation.endian);
642
+ return {
643
+ payload: working,
644
+ summary: {
645
+ index,
646
+ strategy: mutation.strategy,
647
+ detail: `incremented ${mutation.signed ? "signed" : "unsigned"} ${mutation.width}-byte integer at offset ${mutation.offset} by ${mutation.delta}`
648
+ }
649
+ };
650
+ }
651
+ }
652
+ }
653
+ //#endregion
654
+ //#region src/server/domains/protocol-analysis/handlers/shared/network-packet/types.ts
655
+ const ETHER_TYPE_MAP = Object.freeze({
656
+ arp: 2054,
657
+ ipv4: 2048,
658
+ ipv6: 34525,
659
+ vlan: 33024
660
+ });
661
+ const IP_PROTOCOL_MAP = Object.freeze({
662
+ icmp: 1,
663
+ igmp: 2,
664
+ tcp: 6,
665
+ udp: 17,
666
+ gre: 47,
667
+ esp: 50,
668
+ ah: 51,
669
+ icmpv6: 58,
670
+ ospf: 89
671
+ });
672
+ const PCAP_LINK_TYPE_MAP = Object.freeze({
673
+ loopback: 0,
674
+ ethernet: 1,
675
+ raw: 101
676
+ });
677
+ //#endregion
678
+ //#region src/server/domains/protocol-analysis/handlers/shared/network-packet/addressing.ts
679
+ function parseNamedOrNumericValue(value, label, map, max) {
680
+ if (typeof value === "number") {
681
+ if (!Number.isInteger(value) || value < 0 || value > max) throw new Error(`${label} must be an integer between 0 and ${max}`);
682
+ return value;
683
+ }
684
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string or integer`);
685
+ const normalized = value.trim().toLowerCase();
686
+ const mapped = map[normalized];
687
+ if (mapped !== void 0) return mapped;
688
+ if (/^\d+$/.test(normalized)) {
689
+ const parsed = Number.parseInt(normalized, 10);
690
+ if (parsed > max) throw new Error(`${label} must be less than or equal to ${max}`);
691
+ return parsed;
692
+ }
693
+ const hex = normalizeHexString(normalized, label);
694
+ const parsed = Number.parseInt(hex, 16);
695
+ if (parsed > max) throw new Error(`${label} must be less than or equal to ${max}`);
696
+ return parsed;
697
+ }
698
+ function parseMacAddress(value, label) {
699
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${label} must be a non-empty MAC address string`);
700
+ const normalized = value.trim().toLowerCase().replace(/^0x/, "").replace(/[:\-.\s]/g, "");
701
+ if (!/^[0-9a-f]{12}$/i.test(normalized)) throw new Error(`${label} must be a valid 6-byte MAC address`);
702
+ const canonical = normalized.match(/.{2}/g)?.join(":");
703
+ if (!canonical) throw new Error(`${label} must be a valid 6-byte MAC address`);
704
+ return {
705
+ canonical,
706
+ bytes: Buffer.from(normalized, "hex")
707
+ };
708
+ }
709
+ function parseIpv4Address(value, label) {
710
+ if (typeof value !== "string" || isIP(value.trim()) !== 4) throw new Error(`${label} must be a valid IPv4 address`);
711
+ const octets = value.trim().split(".").map((part) => Number.parseInt(part, 10));
712
+ return Buffer.from(octets);
713
+ }
714
+ function parseIpv6Groups(value, label) {
715
+ if (value.length === 0) return [];
716
+ return value.split(":").flatMap((part) => {
717
+ if (part.length === 0) return [];
718
+ if (part.includes(".")) {
719
+ const ipv4 = parseIpv4Address(part, label);
720
+ return [ipv4.readUInt16BE(0).toString(16), ipv4.readUInt16BE(2).toString(16)];
721
+ }
722
+ if (!/^[0-9a-f]{1,4}$/i.test(part)) throw new Error(`${label} contains an invalid IPv6 group`);
723
+ return [part];
724
+ });
725
+ }
726
+ function parseIpv6Address(value, label) {
727
+ if (typeof value !== "string") throw new Error(`${label} must be a valid IPv6 address`);
728
+ const normalized = value.trim().toLowerCase().split("%")[0] ?? "";
729
+ if (isIP(normalized) !== 6) throw new Error(`${label} must be a valid IPv6 address`);
730
+ const segments = normalized.split("::");
731
+ if (segments.length > 2) throw new Error(`${label} must be a valid IPv6 address`);
732
+ const head = parseIpv6Groups(segments[0] ?? "", label);
733
+ const tail = parseIpv6Groups(segments[1] ?? "", label);
734
+ const groups = segments.length === 2 ? [
735
+ ...head,
736
+ ...Array.from({ length: 8 - head.length - tail.length }, () => "0"),
737
+ ...tail
738
+ ] : head;
739
+ if (groups.length !== 8) throw new Error(`${label} must expand to exactly 8 IPv6 groups`);
740
+ const output = Buffer.alloc(16);
741
+ for (const [index, group] of groups.entries()) output.writeUInt16BE(Number.parseInt(group, 16), index * 2);
742
+ return output;
743
+ }
744
+ function parseIpAddress(value, version, label) {
745
+ return version === "ipv4" ? parseIpv4Address(value, label) : parseIpv6Address(value, label);
746
+ }
747
+ function parseEtherType(value, label) {
748
+ return parseNamedOrNumericValue(value, label, ETHER_TYPE_MAP, 65535);
749
+ }
750
+ function parseIpProtocol(value, label) {
751
+ return parseNamedOrNumericValue(value, label, IP_PROTOCOL_MAP, 255);
752
+ }
753
+ function parsePcapLinkType(value, label) {
754
+ return parseNamedOrNumericValue(value, label, PCAP_LINK_TYPE_MAP, 4294967295);
755
+ }
756
+ function parseChecksumEndian(value) {
757
+ return value === "little" ? "little" : "big";
758
+ }
759
+ function parsePacketEndianness(value) {
760
+ return value === "big" ? "big" : "little";
761
+ }
762
+ function parseTimestampPrecision(value) {
763
+ return value === "nano" ? "nano" : "micro";
764
+ }
765
+ function parseHexPayload$1(value, label) {
766
+ if (typeof value !== "string") throw new Error(`${label} must be a hex string`);
767
+ return Buffer.from(normalizeHexString(value, label), "hex");
768
+ }
769
+ //#endregion
770
+ //#region src/server/domains/protocol-analysis/handlers/shared/network-packet/packet-build.ts
771
+ function computeInternetChecksum(buffer) {
772
+ let sum = 0;
773
+ for (let offset = 0; offset < buffer.length; offset += 2) {
774
+ const high = buffer[offset] ?? 0;
775
+ const low = buffer[offset + 1] ?? 0;
776
+ sum += high << 8 | low;
777
+ while (sum > 65535) sum = (sum & 65535) + (sum >>> 16);
778
+ }
779
+ return ~sum & 65535;
780
+ }
781
+ function buildEthernetFrame(destinationMac, sourceMac, etherType, payload) {
782
+ const header = Buffer.alloc(14);
783
+ destinationMac.bytes.copy(header, 0);
784
+ sourceMac.bytes.copy(header, 6);
785
+ header.writeUInt16BE(etherType, 12);
786
+ return Buffer.concat([header, payload]);
787
+ }
788
+ function buildArpPayload(args) {
789
+ if (args.hardwareSize !== args.senderMac.bytes.length || args.hardwareSize !== args.targetMac.bytes.length) throw new Error("hardwareSize must match the provided MAC address lengths");
790
+ if (args.protocolSize !== args.senderIp.length || args.protocolSize !== args.targetIp.length) throw new Error("protocolSize must match the provided IP address lengths");
791
+ const buffer = Buffer.alloc(8 + args.hardwareSize * 2 + args.protocolSize * 2);
792
+ let offset = 0;
793
+ buffer.writeUInt16BE(args.hardwareType, offset);
794
+ offset += 2;
795
+ buffer.writeUInt16BE(args.protocolType, offset);
796
+ offset += 2;
797
+ buffer.writeUInt8(args.hardwareSize, offset++);
798
+ buffer.writeUInt8(args.protocolSize, offset++);
799
+ buffer.writeUInt16BE(args.operation === "reply" ? 2 : 1, offset);
800
+ offset += 2;
801
+ args.senderMac.bytes.copy(buffer, offset);
802
+ offset += args.hardwareSize;
803
+ args.senderIp.copy(buffer, offset);
804
+ offset += args.protocolSize;
805
+ args.targetMac.bytes.copy(buffer, offset);
806
+ offset += args.hardwareSize;
807
+ args.targetIp.copy(buffer, offset);
808
+ return buffer;
809
+ }
810
+ function buildIpv4Packet(args) {
811
+ const header = Buffer.alloc(20);
812
+ header[0] = 69;
813
+ header[1] = (args.dscp & 63) << 2 | args.ecn & 3;
814
+ header.writeUInt16BE(header.length + args.payload.length, 2);
815
+ header.writeUInt16BE(args.identification, 4);
816
+ const flags = (args.dontFragment ? 1 : 0) << 1 | (args.moreFragments ? 1 : 0);
817
+ header.writeUInt16BE((flags & 7) << 13 | args.fragmentOffset & 8191, 6);
818
+ header[8] = args.ttl;
819
+ header[9] = args.protocol;
820
+ header.writeUInt16BE(0, 10);
821
+ args.sourceIp.copy(header, 12);
822
+ args.destinationIp.copy(header, 16);
823
+ const checksum = computeInternetChecksum(header);
824
+ header.writeUInt16BE(checksum, 10);
825
+ return {
826
+ packet: Buffer.concat([header, args.payload]),
827
+ checksum
828
+ };
829
+ }
830
+ function buildIpv6Packet(args) {
831
+ const header = Buffer.alloc(40);
832
+ const versionTrafficFlow = 6 << 28 | (((args.dscp & 63) << 2 | args.ecn & 3) & 255) << 20 | args.flowLabel & 1048575;
833
+ header.writeUInt32BE(versionTrafficFlow >>> 0, 0);
834
+ header.writeUInt16BE(args.payload.length, 4);
835
+ header.writeUInt8(args.protocol, 6);
836
+ header.writeUInt8(args.hopLimit, 7);
837
+ args.sourceIp.copy(header, 8);
838
+ args.destinationIp.copy(header, 24);
839
+ return Buffer.concat([header, args.payload]);
840
+ }
841
+ function buildIcmpEcho(args) {
842
+ const packet = Buffer.alloc(8 + args.payload.length);
843
+ packet[0] = args.operation === "reply" ? 0 : 8;
844
+ packet[1] = 0;
845
+ packet.writeUInt16BE(0, 2);
846
+ packet.writeUInt16BE(args.identifier, 4);
847
+ packet.writeUInt16BE(args.sequenceNumber, 6);
848
+ args.payload.copy(packet, 8);
849
+ const checksum = computeInternetChecksum(packet);
850
+ packet.writeUInt16BE(checksum, 2);
851
+ return {
852
+ packet,
853
+ checksum
854
+ };
855
+ }
856
+ //#endregion
857
+ //#region src/server/domains/protocol-analysis/handlers/shared/network-packet/pcap.ts
858
+ function writeUint32(buffer, offset, value, endianness) {
859
+ if (endianness === "little") buffer.writeUInt32LE(value, offset);
860
+ else buffer.writeUInt32BE(value, offset);
861
+ }
862
+ function writeUint16(buffer, offset, value, endianness) {
863
+ if (endianness === "little") buffer.writeUInt16LE(value, offset);
864
+ else buffer.writeUInt16BE(value, offset);
865
+ }
866
+ function readUint32(buffer, offset, endianness) {
867
+ return endianness === "little" ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
868
+ }
869
+ function readUint16(buffer, offset, endianness) {
870
+ return endianness === "little" ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
871
+ }
872
+ function getPcapMagic(endianness, precision) {
873
+ const hex = endianness === "little" ? precision === "nano" ? "4d3cb2a1" : "d4c3b2a1" : precision === "nano" ? "a1b23c4d" : "a1b2c3d4";
874
+ return Buffer.from(hex, "hex");
875
+ }
876
+ function parsePcapHeader(buffer) {
877
+ if (buffer.length < 24) throw new Error("PCAP file is too small to contain a global header");
878
+ const magic = buffer.subarray(0, 4).toString("hex");
879
+ let endianness;
880
+ let timestampPrecision;
881
+ switch (magic) {
882
+ case "d4c3b2a1":
883
+ endianness = "little";
884
+ timestampPrecision = "micro";
885
+ break;
886
+ case "4d3cb2a1":
887
+ endianness = "little";
888
+ timestampPrecision = "nano";
889
+ break;
890
+ case "a1b2c3d4":
891
+ endianness = "big";
892
+ timestampPrecision = "micro";
893
+ break;
894
+ case "a1b23c4d":
895
+ endianness = "big";
896
+ timestampPrecision = "nano";
897
+ break;
898
+ default: throw new Error("Unsupported capture format: only classic PCAP files are supported");
899
+ }
900
+ return {
901
+ endianness,
902
+ timestampPrecision,
903
+ versionMajor: readUint16(buffer, 4, endianness),
904
+ versionMinor: readUint16(buffer, 6, endianness),
905
+ snapLength: readUint32(buffer, 16, endianness),
906
+ linkType: readUint32(buffer, 20, endianness)
907
+ };
908
+ }
909
+ function parsePcapPacketInput(value, index) {
910
+ if (!isRecord$1(value)) throw new Error(`packets[${index}] must be an object`);
911
+ const data = parseHexPayload$1(value.dataHex, `packets[${index}].dataHex`);
912
+ const timestampSeconds = value.timestampSeconds === void 0 ? 0 : parseNonNegativeInteger(value.timestampSeconds, `packets[${index}].timestampSeconds`);
913
+ const timestampFraction = value.timestampFraction === void 0 ? 0 : parseNonNegativeInteger(value.timestampFraction, `packets[${index}].timestampFraction`);
914
+ const originalLength = value.originalLength === void 0 ? data.length : parsePositiveInteger(value.originalLength, `packets[${index}].originalLength`);
915
+ if (originalLength < data.length) throw new Error(`packets[${index}].originalLength must be >= included packet length`);
916
+ return {
917
+ data,
918
+ timestampSeconds,
919
+ timestampFraction,
920
+ originalLength
921
+ };
922
+ }
923
+ function buildClassicPcap(args) {
924
+ const globalHeader = Buffer.alloc(24);
925
+ getPcapMagic(args.endianness, args.timestampPrecision).copy(globalHeader, 0);
926
+ writeUint16(globalHeader, 4, 2, args.endianness);
927
+ writeUint16(globalHeader, 6, 4, args.endianness);
928
+ writeUint32(globalHeader, 8, 0, args.endianness);
929
+ writeUint32(globalHeader, 12, 0, args.endianness);
930
+ writeUint32(globalHeader, 16, args.snapLength, args.endianness);
931
+ writeUint32(globalHeader, 20, args.linkType, args.endianness);
932
+ const records = args.packets.map((packet) => {
933
+ const header = Buffer.alloc(16);
934
+ writeUint32(header, 0, packet.timestampSeconds, args.endianness);
935
+ writeUint32(header, 4, packet.timestampFraction, args.endianness);
936
+ writeUint32(header, 8, packet.data.length, args.endianness);
937
+ writeUint32(header, 12, packet.originalLength, args.endianness);
938
+ return Buffer.concat([header, packet.data]);
939
+ });
940
+ return Buffer.concat([globalHeader, ...records]);
941
+ }
942
+ function readClassicPcap(buffer, maxPackets, maxBytesPerPacket) {
943
+ const header = parsePcapHeader(buffer);
944
+ const packets = [];
945
+ let offset = 24;
946
+ while (offset < buffer.length) {
947
+ if (maxPackets !== void 0 && packets.length >= maxPackets) break;
948
+ if (offset + 16 > buffer.length) throw new Error("PCAP file ends with an incomplete packet header");
949
+ const timestampSeconds = readUint32(buffer, offset, header.endianness);
950
+ const timestampFraction = readUint32(buffer, offset + 4, header.endianness);
951
+ const includedLength = readUint32(buffer, offset + 8, header.endianness);
952
+ const originalLength = readUint32(buffer, offset + 12, header.endianness);
953
+ offset += 16;
954
+ if (offset + includedLength > buffer.length) throw new Error("PCAP file ends with an incomplete packet payload");
955
+ const packetBytes = buffer.subarray(offset, offset + includedLength);
956
+ offset += includedLength;
957
+ const limit = maxBytesPerPacket === void 0 ? packetBytes.length : maxBytesPerPacket;
958
+ const visibleLength = Math.min(limit, packetBytes.length);
959
+ packets.push({
960
+ index: packets.length,
961
+ timestampSeconds,
962
+ timestampFraction,
963
+ includedLength,
964
+ originalLength,
965
+ dataHex: packetBytes.subarray(0, visibleLength).toString("hex"),
966
+ truncated: visibleLength < packetBytes.length
967
+ });
968
+ }
969
+ return {
970
+ header,
971
+ packets
972
+ };
973
+ }
974
+ //#endregion
975
+ //#region src/modules/protocol-analysis/ProtocolPatternUtils.ts
976
+ const PRINTABLE_MIN = 32;
977
+ const PRINTABLE_MAX = 126;
978
+ const DELIMITER_CANDIDATES = [
979
+ Buffer.from([44]),
980
+ Buffer.from([124]),
981
+ Buffer.from([58]),
982
+ Buffer.from([59]),
983
+ Buffer.from([9]),
984
+ Buffer.from([0]),
985
+ Buffer.from([13, 10])
986
+ ];
987
+ function normalizeHexPayload(value) {
988
+ return value.replace(/^0x/i, "").replace(/\s+/g, "").toLowerCase();
989
+ }
990
+ function isHexPayload(value) {
991
+ const normalized = normalizeHexPayload(value);
992
+ if (normalized.length === 0 || normalized.length % 2 !== 0) return false;
993
+ return /^[0-9a-f]+$/i.test(normalized);
994
+ }
995
+ function parseHexPayload(value) {
996
+ if (!isHexPayload(value)) return null;
997
+ return Buffer.from(normalizeHexPayload(value), "hex");
998
+ }
999
+ function isPrintableByte(value) {
1000
+ return value >= PRINTABLE_MIN && value <= PRINTABLE_MAX;
1001
+ }
1002
+ function printableRatio(buffer) {
1003
+ if (buffer.length === 0) return 0;
1004
+ let printableCount = 0;
1005
+ for (const value of buffer.values()) if (isPrintableByte(value)) printableCount += 1;
1006
+ return printableCount / buffer.length;
1007
+ }
1008
+ function averagePrintableRatio(buffers) {
1009
+ if (buffers.length === 0) return 0;
1010
+ return buffers.reduce((accumulator, buffer) => accumulator + printableRatio(buffer), 0) / buffers.length;
1011
+ }
1012
+ function splitBuffer(buffer, delimiter) {
1013
+ if (delimiter.length === 0) return [buffer];
1014
+ const parts = [];
1015
+ let start = 0;
1016
+ let index = buffer.indexOf(delimiter, start);
1017
+ while (index >= 0) {
1018
+ parts.push(buffer.subarray(start, index));
1019
+ start = index + delimiter.length;
1020
+ index = buffer.indexOf(delimiter, start);
1021
+ }
1022
+ parts.push(buffer.subarray(start));
1023
+ return parts;
1024
+ }
1025
+ function bufferToDelimiterString(buffer) {
1026
+ return printableRatio(buffer) === 1 ? buffer.toString("utf8") : buffer.toString("hex");
1027
+ }
1028
+ function parsePayloads(hexPayloads) {
1029
+ const buffers = [];
1030
+ for (const hexPayload of hexPayloads) {
1031
+ const payload = parseHexPayload(hexPayload);
1032
+ if (payload) buffers.push(payload);
1033
+ }
1034
+ return buffers;
1035
+ }
1036
+ function decodeInteger(buffer, byteOrder) {
1037
+ if (buffer.length === 0) return null;
1038
+ if (buffer.length === 1) return buffer.readUInt8(0);
1039
+ if (buffer.length === 2) return byteOrder === "le" ? buffer.readUInt16LE(0) : buffer.readUInt16BE(0);
1040
+ if (buffer.length === 4) return byteOrder === "le" ? buffer.readUInt32LE(0) : buffer.readUInt32BE(0);
1041
+ if (buffer.length === 8) {
1042
+ const value = byteOrder === "le" ? Number(buffer.readBigUInt64LE(0)) : Number(buffer.readBigUInt64BE(0));
1043
+ return Number.isFinite(value) ? value : null;
1044
+ }
1045
+ let value = 0;
1046
+ const bytes = byteOrder === "le" ? [...buffer.values()].toReversed() : [...buffer.values()];
1047
+ for (const byte of bytes) value = value * 256 + byte;
1048
+ return Number.isFinite(value) ? value : null;
1049
+ }
1050
+ function decodeFloat(buffer, byteOrder) {
1051
+ if (buffer.length === 4) {
1052
+ const value = byteOrder === "le" ? buffer.readFloatLE(0) : buffer.readFloatBE(0);
1053
+ return Number.isFinite(value) ? value : null;
1054
+ }
1055
+ if (buffer.length === 8) {
1056
+ const value = byteOrder === "le" ? buffer.readDoubleLE(0) : buffer.readDoubleBE(0);
1057
+ return Number.isFinite(value) ? value : null;
1058
+ }
1059
+ return null;
1060
+ }
1061
+ function countOccurrences(buffer, delimiter) {
1062
+ if (delimiter.length === 0) return 0;
1063
+ let count = 0;
1064
+ let start = 0;
1065
+ let index = buffer.indexOf(delimiter, start);
1066
+ while (index >= 0) {
1067
+ count += 1;
1068
+ start = index + delimiter.length;
1069
+ index = buffer.indexOf(delimiter, start);
1070
+ }
1071
+ return count;
1072
+ }
1073
+ function inferFieldType(samples) {
1074
+ if (samples.length === 0) return "bytes";
1075
+ if (samples.every((sample) => sample.length === 1) && samples.every((sample) => sample[0] === 0 || sample[0] === 1)) return "bool";
1076
+ if (averagePrintableRatio(samples) >= .7) return "string";
1077
+ if (samples.every((sample) => sample.length === 4) && looksLikeFloatSamples(samples)) return "float";
1078
+ if (samples.every((sample) => sample.length <= 4)) return "int";
1079
+ return "bytes";
1080
+ }
1081
+ function looksLikeFloatSamples(samples) {
1082
+ const decoded = [];
1083
+ for (const sample of samples) {
1084
+ const value = decodeFloat(sample, "be");
1085
+ if (value === null) return false;
1086
+ decoded.push(value);
1087
+ }
1088
+ return decoded.some((value) => Math.abs(value) > .001 && Math.abs(value) < 1e6);
1089
+ }
1090
+ function isPrintableColumn(buffers, offset) {
1091
+ let valueCount = 0;
1092
+ let printableCount = 0;
1093
+ for (const buffer of buffers) {
1094
+ const value = buffer[offset];
1095
+ if (value === void 0) continue;
1096
+ valueCount += 1;
1097
+ if (isPrintableByte(value)) printableCount += 1;
1098
+ }
1099
+ return valueCount > 0 && printableCount / valueCount >= .8;
1100
+ }
1101
+ function isBooleanColumn(buffers, offset) {
1102
+ let valueCount = 0;
1103
+ for (const buffer of buffers) {
1104
+ const value = buffer[offset];
1105
+ if (value === void 0) continue;
1106
+ valueCount += 1;
1107
+ if (value !== 0 && value !== 1) return false;
1108
+ }
1109
+ return valueCount > 0;
1110
+ }
1111
+ function buildDelimitedFields(buffers, delimiter) {
1112
+ if (delimiter.length === 0) return [];
1113
+ const tokenized = buffers.map((buffer) => splitBuffer(buffer, delimiter));
1114
+ const firstRow = tokenized[0];
1115
+ if (!firstRow || firstRow.length < 2) return [];
1116
+ const tokenCount = firstRow.length;
1117
+ if (!tokenized.every((parts) => parts.length === tokenCount)) return [];
1118
+ const fields = [];
1119
+ let currentOffset = 0;
1120
+ for (let index = 0; index < tokenCount; index += 1) {
1121
+ const template = firstRow[index];
1122
+ if (!template) continue;
1123
+ const samples = tokenized.map((parts) => parts[index]).filter((part) => Buffer.isBuffer(part));
1124
+ fields.push({
1125
+ name: `field_${index + 1}`,
1126
+ offset: currentOffset,
1127
+ length: template.length,
1128
+ type: inferFieldType(samples)
1129
+ });
1130
+ currentOffset += template.length + delimiter.length;
1131
+ }
1132
+ return fields;
1133
+ }
1134
+ function buildFixedWidthFields(buffers) {
1135
+ const minLength = Math.min(...buffers.map((buffer) => buffer.length));
1136
+ const fields = [];
1137
+ let offset = 0;
1138
+ while (offset < minLength && fields.length < 24) {
1139
+ if (isPrintableColumn(buffers, offset)) {
1140
+ let end = offset + 1;
1141
+ while (end < minLength && isPrintableColumn(buffers, end)) end += 1;
1142
+ fields.push({
1143
+ name: `field_${fields.length + 1}`,
1144
+ offset,
1145
+ length: end - offset,
1146
+ type: "string"
1147
+ });
1148
+ offset = end;
1149
+ continue;
1150
+ }
1151
+ if (isBooleanColumn(buffers, offset)) {
1152
+ fields.push({
1153
+ name: `field_${fields.length + 1}`,
1154
+ offset,
1155
+ length: 1,
1156
+ type: "bool"
1157
+ });
1158
+ offset += 1;
1159
+ continue;
1160
+ }
1161
+ const remaining = minLength - offset;
1162
+ if (remaining >= 4) {
1163
+ if (looksLikeFloatSamples(buffers.map((buffer) => buffer.subarray(offset, offset + 4)))) {
1164
+ fields.push({
1165
+ name: `field_${fields.length + 1}`,
1166
+ offset,
1167
+ length: 4,
1168
+ type: "float"
1169
+ });
1170
+ offset += 4;
1171
+ continue;
1172
+ }
1173
+ }
1174
+ const segmentLength = remaining >= 4 ? 4 : Math.min(remaining, 2);
1175
+ const samples = buffers.map((buffer) => buffer.subarray(offset, offset + segmentLength));
1176
+ fields.push({
1177
+ name: `field_${fields.length + 1}`,
1178
+ offset,
1179
+ length: segmentLength,
1180
+ type: inferFieldType(samples)
1181
+ });
1182
+ offset += segmentLength;
1183
+ }
1184
+ return fields;
1185
+ }
1186
+ function inferDelimiter(buffers) {
1187
+ for (const candidate of DELIMITER_CANDIDATES) {
1188
+ const counts = buffers.map((buffer) => countOccurrences(buffer, candidate));
1189
+ const firstCount = counts[0];
1190
+ if (typeof firstCount === "number" && firstCount >= 2 && counts.every((count) => count === firstCount)) return bufferToDelimiterString(candidate);
1191
+ }
1192
+ }
1193
+ function inferByteOrder(buffers) {
1194
+ const minLength = Math.min(...buffers.map((buffer) => buffer.length));
1195
+ if (minLength < 2) return "be";
1196
+ let leScore = 0;
1197
+ let beScore = 0;
1198
+ const limit = Math.min(minLength - 1, 8);
1199
+ for (let offset = 0; offset < limit; offset += 2) {
1200
+ let leSmallValues = 0;
1201
+ let beSmallValues = 0;
1202
+ for (const buffer of buffers) {
1203
+ const little = buffer.readUInt16LE(offset);
1204
+ const big = buffer.readUInt16BE(offset);
1205
+ if (little < 4096) leSmallValues += 1;
1206
+ if (big < 4096) beSmallValues += 1;
1207
+ }
1208
+ if (leSmallValues > beSmallValues) leScore += 1;
1209
+ else if (beSmallValues > leSmallValues) beScore += 1;
1210
+ }
1211
+ return leScore > beScore ? "le" : "be";
1212
+ }
1213
+ function labelMagicFields(fields, buffers) {
1214
+ if (fields.length === 0 || buffers.length < 2) return fields;
1215
+ const minLen = Math.min(...buffers.map((b) => b.length));
1216
+ let commonPrefixLen = 0;
1217
+ for (let offset = 0; offset < minLen; offset += 1) {
1218
+ const byte = buffers[0][offset];
1219
+ if (buffers.every((b) => b[offset] === byte)) commonPrefixLen = offset + 1;
1220
+ else break;
1221
+ }
1222
+ if (commonPrefixLen === 0) return fields;
1223
+ let magicLabelApplied = false;
1224
+ return fields.map((field) => {
1225
+ if (!magicLabelApplied && field.offset === 0 && commonPrefixLen >= 2) {
1226
+ magicLabelApplied = true;
1227
+ return {
1228
+ ...field,
1229
+ name: "magic"
1230
+ };
1231
+ }
1232
+ if (magicLabelApplied && field.type === "int" && field.length <= 2 && field.offset <= commonPrefixLen) return {
1233
+ ...field,
1234
+ name: "version"
1235
+ };
1236
+ return field;
1237
+ });
1238
+ }
1239
+ //#endregion
1240
+ //#region src/modules/protocol-analysis/ProtocolPatternEngine.ts
1241
+ var ProtocolPatternEngine = class {
1242
+ patterns = /* @__PURE__ */ new Map();
1243
+ legacyPatterns = /* @__PURE__ */ new Map();
1244
+ definePattern(name, specOrFields, options) {
1245
+ const legacyPattern = Array.isArray(specOrFields) ? this.createLegacyPattern(name, specOrFields, options) : this.createLegacyPatternFromSpec(name, specOrFields);
1246
+ const spec = this.createSpecFromLegacyPattern(legacyPattern);
1247
+ this.patterns.set(name, spec);
1248
+ this.legacyPatterns.set(name, legacyPattern);
1249
+ if (Array.isArray(specOrFields)) return legacyPattern;
1250
+ }
1251
+ detectPattern(hexPayload) {
1252
+ const payload = parseHexPayload(hexPayload);
1253
+ if (!payload) return null;
1254
+ let bestMatch = null;
1255
+ for (const pattern of this.patterns.values()) {
1256
+ const totalChecks = pattern.fields.length + (pattern.fieldDelimiter ? 1 : 0);
1257
+ if (totalChecks === 0) continue;
1258
+ let matches = 0;
1259
+ if (pattern.fieldDelimiter && this.payloadContainsDelimiter(payload, pattern.fieldDelimiter)) matches += 1;
1260
+ for (const field of pattern.fields) if (this.matchesField(payload, field, pattern.byteOrder ?? "be")) matches += 1;
1261
+ const confidence = Number((matches / totalChecks).toFixed(2));
1262
+ if (confidence <= 0) continue;
1263
+ const candidate = {
1264
+ pattern,
1265
+ confidence,
1266
+ matches,
1267
+ total: totalChecks
1268
+ };
1269
+ if (!bestMatch || candidate.confidence > bestMatch.confidence || candidate.confidence === bestMatch.confidence && candidate.matches > bestMatch.matches) bestMatch = candidate;
1270
+ }
1271
+ return bestMatch;
1272
+ }
1273
+ autoDetect(hexPayloads) {
1274
+ const buffers = parsePayloads(hexPayloads);
1275
+ if (buffers.length === 0) return null;
1276
+ const delimiter = inferDelimiter(buffers);
1277
+ const fields = this.inferFields(hexPayloads);
1278
+ return {
1279
+ name: "auto-detected-pattern",
1280
+ fieldDelimiter: delimiter,
1281
+ byteOrder: inferByteOrder(buffers),
1282
+ fields
1283
+ };
1284
+ }
1285
+ inferFields(hexPayloads) {
1286
+ const buffers = parsePayloads(hexPayloads);
1287
+ if (buffers.length === 0) return [];
1288
+ const delimiter = inferDelimiter(buffers);
1289
+ if (delimiter) {
1290
+ const fields = buildDelimitedFields(buffers, this.parseDelimiter(delimiter));
1291
+ if (fields.length > 0) return labelMagicFields(fields, buffers);
1292
+ }
1293
+ return labelMagicFields(buildFixedWidthFields(buffers), buffers);
1294
+ }
1295
+ autoDetectPattern(payloads, options) {
1296
+ const hexPayloads = payloads.map((payload) => payload.toString("hex"));
1297
+ const detected = this.autoDetect(hexPayloads);
1298
+ const name = options?.name ?? detected?.name ?? "auto_detected";
1299
+ if (!detected) {
1300
+ const emptyPattern = this.createLegacyPattern(name, []);
1301
+ this.patterns.set(name, this.createSpecFromLegacyPattern(emptyPattern));
1302
+ this.legacyPatterns.set(name, emptyPattern);
1303
+ return emptyPattern;
1304
+ }
1305
+ const namedPattern = {
1306
+ ...detected,
1307
+ name
1308
+ };
1309
+ this.definePattern(name, namedPattern);
1310
+ return this.getPattern(name) ?? this.createLegacyPatternFromSpec(name, namedPattern);
1311
+ }
1312
+ getPattern(name) {
1313
+ return this.legacyPatterns.get(name);
1314
+ }
1315
+ listPatterns() {
1316
+ return [...this.patterns.keys()];
1317
+ }
1318
+ exportProto(pattern) {
1319
+ const legacyPattern = this.isLegacyPattern(pattern) ? pattern : this.createLegacyPatternFromSpec(pattern.name, pattern);
1320
+ const lines = [
1321
+ `// Protocol: ${legacyPattern.name}`,
1322
+ `// Byte order: ${legacyPattern.byteOrder}`,
1323
+ ""
1324
+ ];
1325
+ if (legacyPattern.encryption) {
1326
+ lines.push(`// Encryption: ${legacyPattern.encryption.type}`);
1327
+ if (legacyPattern.encryption.notes) lines.push(`// Notes: ${legacyPattern.encryption.notes}`);
1328
+ lines.push("");
1329
+ }
1330
+ lines.push(`message ${this.toPascalCase(legacyPattern.name)} {`);
1331
+ for (let index = 0; index < legacyPattern.fields.length; index += 1) {
1332
+ const field = legacyPattern.fields[index];
1333
+ if (!field) continue;
1334
+ const comment = field.description ? ` // ${field.description}` : "";
1335
+ lines.push(` ${this.toProtoType(field.type)} ${field.name} = ${index + 1};${comment}`);
1336
+ }
1337
+ lines.push("}");
1338
+ lines.push("");
1339
+ return lines.join("\n");
1340
+ }
1341
+ payloadContainsDelimiter(payload, delimiter) {
1342
+ const delimiterBuffer = this.parseDelimiter(delimiter);
1343
+ if (delimiterBuffer.length === 0) return false;
1344
+ return payload.includes(delimiterBuffer);
1345
+ }
1346
+ matchesField(payload, field, byteOrder) {
1347
+ if (field.offset < 0 || field.length <= 0 || payload.length < field.offset + field.length) return false;
1348
+ const slice = payload.subarray(field.offset, field.offset + field.length);
1349
+ switch (field.type) {
1350
+ case "bytes": return slice.length === field.length;
1351
+ case "bool": return slice.length === 1 && (slice[0] === 0 || slice[0] === 1);
1352
+ case "string": return printableRatio(slice) >= .6;
1353
+ case "int": return decodeInteger(slice, byteOrder) !== null;
1354
+ case "float": return decodeFloat(slice, byteOrder) !== null;
1355
+ default: return false;
1356
+ }
1357
+ }
1358
+ createLegacyPattern(name, fields, options) {
1359
+ return {
1360
+ name,
1361
+ fields: fields.map((field) => ({
1362
+ name: field.name,
1363
+ offset: field.offset,
1364
+ length: field.length,
1365
+ type: field.type,
1366
+ ...field.description ? { description: field.description } : {}
1367
+ })).toSorted((left, right) => left.offset - right.offset),
1368
+ byteOrder: options?.byteOrder ?? "big",
1369
+ ...options?.encryption ? { encryption: options.encryption } : {}
1370
+ };
1371
+ }
1372
+ createLegacyPatternFromSpec(name, spec) {
1373
+ return {
1374
+ name,
1375
+ fieldDelimiter: spec.fieldDelimiter,
1376
+ byteOrder: spec.byteOrder === "le" ? "little" : "big",
1377
+ fields: spec.fields.map((field) => ({
1378
+ name: field.name,
1379
+ offset: field.offset,
1380
+ length: field.length,
1381
+ type: this.toLegacyFieldType(field),
1382
+ ...field.description ? { description: field.description } : {}
1383
+ }))
1384
+ };
1385
+ }
1386
+ createSpecFromLegacyPattern(pattern) {
1387
+ return {
1388
+ name: pattern.name,
1389
+ fieldDelimiter: pattern.fieldDelimiter,
1390
+ byteOrder: pattern.byteOrder === "little" ? "le" : "be",
1391
+ fields: pattern.fields.map((field) => ({
1392
+ name: field.name,
1393
+ offset: field.offset,
1394
+ length: field.length,
1395
+ type: this.toSpecFieldType(field.type),
1396
+ ...field.description ? { description: field.description } : {}
1397
+ }))
1398
+ };
1399
+ }
1400
+ isLegacyPattern(pattern) {
1401
+ return pattern.byteOrder === "big" || pattern.byteOrder === "little";
1402
+ }
1403
+ toLegacyFieldType(field) {
1404
+ if (field.type === "float") return "float";
1405
+ if (field.type === "string") return "string";
1406
+ if (field.type === "bytes") return "bytes";
1407
+ if (field.length === 1) return "uint8";
1408
+ if (field.length === 2) return "uint16";
1409
+ if (field.length === 4) return "uint32";
1410
+ return "int64";
1411
+ }
1412
+ toSpecFieldType(fieldType) {
1413
+ if (fieldType === "float") return "float";
1414
+ if (fieldType === "string") return "string";
1415
+ if (fieldType === "bytes") return "bytes";
1416
+ return "int";
1417
+ }
1418
+ parseDelimiter(delimiter) {
1419
+ if (isHexPayload(delimiter)) {
1420
+ const parsed = parseHexPayload(delimiter);
1421
+ if (parsed) return parsed;
1422
+ }
1423
+ return Buffer.from(delimiter, "utf8");
1424
+ }
1425
+ toProtoType(type) {
1426
+ return {
1427
+ uint8: "uint32",
1428
+ uint16: "uint32",
1429
+ uint32: "uint32",
1430
+ int64: "int64",
1431
+ float: "float",
1432
+ string: "string",
1433
+ bytes: "bytes"
1434
+ }[type];
1435
+ }
1436
+ toPascalCase(name) {
1437
+ return name.replace(/[^a-zA-Z0-9_]/g, "_").split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("") || "Message";
1438
+ }
1439
+ };
1440
+ //#endregion
1441
+ //#region src/modules/protocol-analysis/StateMachineInferrer.ts
1442
+ function isRecord(value) {
1443
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1444
+ }
1445
+ function normalizeText(value) {
1446
+ return value.trim().toLowerCase();
1447
+ }
1448
+ function calculateEntropy(buffer) {
1449
+ if (buffer.length === 0) return 0;
1450
+ const frequency = /* @__PURE__ */ new Map();
1451
+ for (const byte of buffer) frequency.set(byte, (frequency.get(byte) ?? 0) + 1);
1452
+ let entropy = 0;
1453
+ for (const count of frequency.values()) {
1454
+ const p = count / buffer.length;
1455
+ if (p > 0) entropy -= p * Math.log2(p);
1456
+ }
1457
+ return entropy;
1458
+ }
1459
+ function printableRatioOf(value) {
1460
+ if (value.length === 0) return 0;
1461
+ let count = 0;
1462
+ for (let i = 0; i < value.length; i += 1) {
1463
+ const code = value.charCodeAt(i);
1464
+ if (code >= 32 && code <= 126) count += 1;
1465
+ }
1466
+ return count / value.length;
1467
+ }
1468
+ var StateMachineInferrer = class {
1469
+ infer(messages) {
1470
+ if (messages.length === 0) return {
1471
+ states: [],
1472
+ transitions: [],
1473
+ initial: "",
1474
+ initialState: "",
1475
+ finalStates: []
1476
+ };
1477
+ const statesBySignature = /* @__PURE__ */ new Map();
1478
+ const transitionsByKey = /* @__PURE__ */ new Map();
1479
+ let previousStateId = "";
1480
+ for (let index = 0; index < messages.length; index += 1) {
1481
+ const message = messages[index];
1482
+ if (!message) continue;
1483
+ const signature = this.buildSignature(message);
1484
+ let state = statesBySignature.get(signature);
1485
+ if (!state) {
1486
+ state = {
1487
+ id: `state_${statesBySignature.size + 1}`,
1488
+ name: this.buildStateName(message, statesBySignature.size + 1),
1489
+ type: this.inferStateType(message, index === messages.length - 1)
1490
+ };
1491
+ statesBySignature.set(signature, state);
1492
+ } else state.type = this.mergeStateTypes(state.type, this.inferStateType(message, index === messages.length - 1));
1493
+ if (previousStateId && message.timestamp !== void 0) {
1494
+ const firstMessage = messages[0];
1495
+ if (firstMessage && firstMessage.timestamp !== void 0) state.timeout = message.timestamp - firstMessage.timestamp;
1496
+ }
1497
+ if (previousStateId) {
1498
+ const event = this.buildEventName(message);
1499
+ const condition = this.buildCondition(message.fields);
1500
+ const action = this.buildAction(message);
1501
+ const transitionKey = `${previousStateId}:${state.id}:${event}`;
1502
+ if (!transitionsByKey.has(transitionKey)) transitionsByKey.set(transitionKey, {
1503
+ from: previousStateId,
1504
+ to: state.id,
1505
+ event,
1506
+ confidence: this.computeTransitionConfidence(message),
1507
+ ...condition ? { condition } : {},
1508
+ ...action ? { action } : {}
1509
+ });
1510
+ }
1511
+ previousStateId = state.id;
1512
+ }
1513
+ const firstMessage = messages[0];
1514
+ const initial = firstMessage ? statesBySignature.get(this.buildSignature(firstMessage))?.id ?? "" : "";
1515
+ return {
1516
+ states: [...statesBySignature.values()],
1517
+ transitions: [...transitionsByKey.values()],
1518
+ initial,
1519
+ initialState: initial,
1520
+ finalStates: this.collectTerminalStates([...statesBySignature.values()])
1521
+ };
1522
+ }
1523
+ visualize(machine) {
1524
+ if (machine.states.length === 0) return [
1525
+ "```mermaid",
1526
+ "stateDiagram-v2",
1527
+ " [*] --> empty",
1528
+ "```"
1529
+ ].join("\n");
1530
+ const lines = ["```mermaid", "stateDiagram-v2"];
1531
+ const initial = machine.initialState ?? machine.initial;
1532
+ if (initial) lines.push(` [*] --> ${initial}`);
1533
+ for (const state of machine.states) {
1534
+ const stateType = state.type ?? "normal";
1535
+ const label = stateType === "normal" ? state.name : `${state.name} (${stateType})`;
1536
+ lines.push(` state "${label}" as ${state.id}`);
1537
+ }
1538
+ for (const transition of machine.transitions) {
1539
+ const parts = [transition.event ?? transition.trigger ?? "transition"];
1540
+ if (typeof transition.confidence === "number") parts.push(`(${transition.confidence.toFixed(2)})`);
1541
+ if (transition.condition) parts.push(`[${transition.condition}]`);
1542
+ if (transition.action) parts.push(`/ ${transition.action}`);
1543
+ lines.push(` ${transition.from} --> ${transition.to} : ${parts.join(" ")}`);
1544
+ }
1545
+ const finalStateSet = new Set(machine.finalStates ?? []);
1546
+ for (const state of machine.states) {
1547
+ const stateType = state.type ?? "normal";
1548
+ if (stateType === "accept" || stateType === "reject" || finalStateSet.has(state.id)) lines.push(` ${state.id} --> [*]`);
1549
+ }
1550
+ lines.push("```");
1551
+ return lines.join("\n");
1552
+ }
1553
+ inferStateMachine(messages) {
1554
+ const normalizedMessages = messages.map((message) => ({
1555
+ direction: message.direction === "out" ? "req" : "res",
1556
+ timestamp: message.timestamp ?? 0,
1557
+ fields: {},
1558
+ raw: message.payload.length > 0 ? message.payload.toString("utf8") : message.payload.toString("hex"),
1559
+ rawBuffer: message.payload
1560
+ }));
1561
+ return this.infer(normalizedMessages);
1562
+ }
1563
+ generateMermaid(machine) {
1564
+ return this.visualize(machine);
1565
+ }
1566
+ simplify(machine) {
1567
+ if (machine.states.length < 2) return {
1568
+ ...machine,
1569
+ initialState: machine.initialState ?? machine.initial,
1570
+ finalStates: machine.finalStates ?? this.collectTerminalStates(machine.states)
1571
+ };
1572
+ const stateToGroup = /* @__PURE__ */ new Map();
1573
+ const groupRepresentative = /* @__PURE__ */ new Map();
1574
+ for (const state of machine.states) {
1575
+ const prefix = this.getPayloadPrefix(state);
1576
+ if (!prefix) continue;
1577
+ const existingGroup = [...groupRepresentative.entries()].find(([key]) => key === prefix);
1578
+ if (existingGroup) stateToGroup.set(state.id, existingGroup[0]);
1579
+ else {
1580
+ groupRepresentative.set(prefix, state.id);
1581
+ stateToGroup.set(state.id, prefix);
1582
+ }
1583
+ }
1584
+ const mergeMap = /* @__PURE__ */ new Map();
1585
+ const groupIdToPrimary = /* @__PURE__ */ new Map();
1586
+ for (const [prefix, primaryId] of groupRepresentative) groupIdToPrimary.set(prefix, primaryId);
1587
+ for (const state of machine.states) {
1588
+ const prefix = this.getPayloadPrefix(state);
1589
+ if (!prefix) continue;
1590
+ const primary = groupIdToPrimary.get(prefix);
1591
+ if (primary && primary !== state.id) mergeMap.set(state.id, primary);
1592
+ }
1593
+ if (mergeMap.size === 0) return {
1594
+ ...machine,
1595
+ initialState: machine.initialState ?? machine.initial,
1596
+ finalStates: machine.finalStates ?? this.collectTerminalStates(machine.states)
1597
+ };
1598
+ const newStates = machine.states.filter((state) => !mergeMap.has(state.id));
1599
+ const newTransitions = machine.transitions.map((t) => ({
1600
+ ...t,
1601
+ from: mergeMap.get(t.from) ?? t.from,
1602
+ to: mergeMap.get(t.to) ?? t.to
1603
+ })).filter((t) => t.from !== t.to);
1604
+ const rawInitialState = machine.initialState ?? machine.initial ?? "";
1605
+ const initialState = mergeMap.get(rawInitialState) ?? rawInitialState;
1606
+ return {
1607
+ states: newStates,
1608
+ transitions: newTransitions,
1609
+ initial: initialState,
1610
+ initialState,
1611
+ finalStates: machine.finalStates.map((fs) => mergeMap.get(fs) ?? fs).filter((fs, index, arr) => arr.indexOf(fs) === index)
1612
+ };
1613
+ }
1614
+ getPayloadPrefix(state) {
1615
+ const payload = state.expectedPayload;
1616
+ if (!payload || payload.length < 8) return null;
1617
+ return payload.slice(0, 8).toLowerCase();
1618
+ }
1619
+ buildSignature(message) {
1620
+ const fieldKeys = Object.keys(message.fields).toSorted().join(",");
1621
+ const rawPrefix = normalizeText(message.rawBuffer ? message.rawBuffer.toString("hex") : message.raw).slice(0, 24);
1622
+ return `${message.direction}|${fieldKeys}|${rawPrefix}`;
1623
+ }
1624
+ buildStateName(message, position) {
1625
+ const directionName = message.direction === "req" ? "send" : "recv";
1626
+ const primaryField = this.findPrimaryFieldName(message.fields);
1627
+ const raw = message.raw;
1628
+ if (raw.length === 0) return `${directionName}_empty`;
1629
+ const buf = message.rawBuffer;
1630
+ const hexContent = Buffer.isBuffer(buf) ? buf.toString("hex") : raw;
1631
+ if (hexContent.startsWith("16") || hexContent.startsWith("15") || hexContent.startsWith("17")) return `${directionName}_tls_handshake`;
1632
+ const trimmed = raw.trimStart();
1633
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return `${directionName}_json_${primaryField || `step_${position}`}`;
1634
+ if (printableRatioOf(raw) >= .7) {
1635
+ const lower = normalizeText(raw);
1636
+ if (lower.includes("close") || lower.includes("fin") || lower.includes("bye")) return `${directionName}_close`;
1637
+ if (lower.startsWith("get ") || lower.startsWith("post ") || lower.startsWith("http")) return `${directionName}_text_http`;
1638
+ return `${directionName}_text_${primaryField || `step_${position}`}`;
1639
+ }
1640
+ if (Buffer.isBuffer(buf) && buf.length >= 32) {
1641
+ if (calculateEntropy(buf) > 6) return `${directionName}_encrypted`;
1642
+ }
1643
+ if (Buffer.isBuffer(buf) && buf.length <= 4) return `${directionName}_control`;
1644
+ return `${directionName}_${primaryField || `step_${position}`}`;
1645
+ }
1646
+ findPrimaryFieldName(fields) {
1647
+ const firstKey = Object.keys(fields).toSorted()[0];
1648
+ return firstKey ? firstKey : "";
1649
+ }
1650
+ inferStateType(message, isLastMessage) {
1651
+ const text = normalizeText(message.raw);
1652
+ const statusValue = this.findStatusValue(message.fields);
1653
+ if (this.containsRejectSignal(text) || this.containsRejectSignal(statusValue)) return "reject";
1654
+ if (this.containsAcceptSignal(text) || this.containsAcceptSignal(statusValue) || isLastMessage && message.direction === "res") return "accept";
1655
+ return "normal";
1656
+ }
1657
+ mergeStateTypes(current, next) {
1658
+ if (current === "reject" || next === "reject") return "reject";
1659
+ if (current === "accept" || next === "accept") return "accept";
1660
+ return "normal";
1661
+ }
1662
+ findStatusValue(fields) {
1663
+ for (const key of [
1664
+ "status",
1665
+ "result",
1666
+ "code",
1667
+ "reason",
1668
+ "message"
1669
+ ]) {
1670
+ const value = fields[key];
1671
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
1672
+ }
1673
+ return "";
1674
+ }
1675
+ containsRejectSignal(value) {
1676
+ return [
1677
+ "error",
1678
+ "fail",
1679
+ "denied",
1680
+ "reject",
1681
+ "timeout",
1682
+ "invalid"
1683
+ ].some((token) => normalizeText(value).includes(token));
1684
+ }
1685
+ containsAcceptSignal(value) {
1686
+ return [
1687
+ "ok",
1688
+ "success",
1689
+ "accept",
1690
+ "ready",
1691
+ "done",
1692
+ "complete"
1693
+ ].some((token) => normalizeText(value).includes(token));
1694
+ }
1695
+ buildEventName(message) {
1696
+ const statusValue = this.findStatusValue(message.fields);
1697
+ if (statusValue) return `${message.direction}_${normalizeText(statusValue).replace(/[^a-z0-9]+/g, "_")}`;
1698
+ const primaryField = this.findPrimaryFieldName(message.fields);
1699
+ if (primaryField) return `${message.direction}_${primaryField}`;
1700
+ return `${message.direction}_message`;
1701
+ }
1702
+ buildCondition(fields) {
1703
+ const statusValue = this.findStatusValue(fields);
1704
+ if (statusValue) return `status=${statusValue}`;
1705
+ const keys = Object.keys(fields).toSorted().slice(0, 2);
1706
+ if (keys.length === 0) return;
1707
+ const fragments = [];
1708
+ for (const key of keys) {
1709
+ const value = fields[key];
1710
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") fragments.push(`${key}=${value}`);
1711
+ }
1712
+ return fragments.length > 0 ? fragments.join(", ") : void 0;
1713
+ }
1714
+ buildAction(message) {
1715
+ const statusValue = this.findStatusValue(message.fields);
1716
+ if (this.containsRejectSignal(statusValue) || this.containsRejectSignal(message.raw)) return "reject";
1717
+ if (this.containsAcceptSignal(statusValue) || this.containsAcceptSignal(message.raw)) return "complete";
1718
+ const rawText = normalizeText(message.raw);
1719
+ if (rawText.includes("ack")) return "acknowledge";
1720
+ if (rawText.includes("retry")) return "retry";
1721
+ if (Object.keys(message.fields).length > 0 && isRecord(message.fields)) return message.direction === "req" ? "send" : "receive";
1722
+ }
1723
+ collectTerminalStates(states) {
1724
+ const terminalIds = states.filter((state) => {
1725
+ const stateType = state.type ?? "normal";
1726
+ return stateType === "accept" || stateType === "reject";
1727
+ }).map((state) => state.id);
1728
+ for (const state of states) {
1729
+ const lower = normalizeText(state.name);
1730
+ if (lower.includes("close") || lower.includes("fin") || lower.includes("bye")) {
1731
+ if (!terminalIds.includes(state.id)) terminalIds.push(state.id);
1732
+ }
1733
+ }
1734
+ return terminalIds;
1735
+ }
1736
+ computeTransitionConfidence(message) {
1737
+ let confidence = .3;
1738
+ if (Object.keys(message.fields).length > 0) confidence += .3;
1739
+ if (this.findStatusValue(message.fields)) confidence += .2;
1740
+ if (message.raw.length > 0) confidence += .2;
1741
+ return Math.min(confidence, 1);
1742
+ }
1743
+ };
1744
+ //#endregion
1745
+ //#region src/server/domains/protocol-analysis/handlers/base.ts
1746
+ const EMPTY_STATE_MACHINE = {
1747
+ states: [],
1748
+ transitions: [],
1749
+ initial: "",
1750
+ initialState: "",
1751
+ finalStates: []
1752
+ };
1753
+ var ProtocolAnalysisBaseHandlers = class {
1754
+ engine;
1755
+ inferrer;
1756
+ eventBus;
1757
+ constructor(engine, inferrer, eventBus) {
1758
+ this.engine = engine;
1759
+ this.inferrer = inferrer;
1760
+ this.eventBus = eventBus;
1761
+ }
1762
+ emitEvent(event, payload) {
1763
+ this.eventBus?.emit(event, {
1764
+ ...payload,
1765
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1766
+ });
1767
+ }
1768
+ getEngine() {
1769
+ if (!this.engine) this.engine = new ProtocolPatternEngine();
1770
+ return this.engine;
1771
+ }
1772
+ getInferrer() {
1773
+ if (!this.inferrer) this.inferrer = new StateMachineInferrer();
1774
+ return this.inferrer;
1775
+ }
1776
+ errorMessage(error) {
1777
+ return error instanceof Error ? error.message : String(error);
1778
+ }
1779
+ };
1780
+ //#endregion
1781
+ //#region src/server/domains/protocol-analysis/handlers/pattern-handlers.ts
1782
+ var ProtocolAnalysisPatternHandlers = class extends ProtocolAnalysisBaseHandlers {
1783
+ async handleDefinePattern(args) {
1784
+ try {
1785
+ const name = typeof args.name === "string" && args.name.trim().length > 0 ? args.name : "unnamed_pattern";
1786
+ const specObject = argObject(args, "spec");
1787
+ if (specObject) {
1788
+ const spec = parsePatternSpec(name, specObject);
1789
+ this.getEngine().definePattern(name, spec);
1790
+ return {
1791
+ patternId: name,
1792
+ pattern: this.getEngine().getPattern(name) ?? {
1793
+ name,
1794
+ fields: [],
1795
+ byteOrder: "big"
1796
+ },
1797
+ success: true
1798
+ };
1799
+ }
1800
+ const fields = (Array.isArray(args.fields) ? args.fields : []).map((field, index) => parseLegacyField(field, index));
1801
+ const byteOrder = args.byteOrder === "little" || args.byteOrder === "big" ? args.byteOrder : void 0;
1802
+ const encryption = parseEncryptionInfo(args.encryption);
1803
+ return {
1804
+ patternId: name,
1805
+ pattern: this.getEngine().definePattern(name, fields, {
1806
+ ...byteOrder ? { byteOrder } : {},
1807
+ ...encryption ? { encryption } : {}
1808
+ }),
1809
+ success: true
1810
+ };
1811
+ } catch (error) {
1812
+ return {
1813
+ patternId: "error",
1814
+ pattern: {
1815
+ name: "error",
1816
+ fields: [],
1817
+ byteOrder: "big"
1818
+ },
1819
+ success: false,
1820
+ error: this.errorMessage(error)
1821
+ };
1822
+ }
1823
+ }
1824
+ async handleAutoDetect(args) {
1825
+ try {
1826
+ const hexPayloads = (() => {
1827
+ const newPayloads = argStringArray(args, "hexPayloads");
1828
+ if (newPayloads.length > 0) return newPayloads;
1829
+ return argStringArray(args, "payloads");
1830
+ })();
1831
+ const detected = this.getEngine().autoDetect(hexPayloads);
1832
+ const patternName = typeof args.name === "string" && args.name.trim().length > 0 ? args.name : void 0;
1833
+ if (!detected) return {
1834
+ patterns: [this.getEngine().autoDetectPattern([], patternName ? { name: patternName } : {})],
1835
+ success: true
1836
+ };
1837
+ const namedPattern = {
1838
+ ...detected,
1839
+ name: patternName ?? detected.name
1840
+ };
1841
+ this.getEngine().definePattern(namedPattern.name, namedPattern);
1842
+ const result = this.getEngine().getPattern(namedPattern.name) ?? {
1843
+ name: namedPattern.name,
1844
+ fields: [],
1845
+ byteOrder: "big"
1846
+ };
1847
+ this.emitEvent("protocol:pattern_detected", {
1848
+ patternName: namedPattern.name,
1849
+ confidence: 0
1850
+ });
1851
+ return {
1852
+ patterns: [result],
1853
+ success: true
1854
+ };
1855
+ } catch (error) {
1856
+ return {
1857
+ patterns: [],
1858
+ success: false,
1859
+ error: this.errorMessage(error)
1860
+ };
1861
+ }
1862
+ }
1863
+ async handleInferFields(args) {
1864
+ try {
1865
+ const hexPayloads = argStringArray(args, "hexPayloads");
1866
+ return {
1867
+ success: true,
1868
+ fields: this.getEngine().inferFields(hexPayloads)
1869
+ };
1870
+ } catch (error) {
1871
+ return {
1872
+ fields: [],
1873
+ success: false,
1874
+ error: this.errorMessage(error)
1875
+ };
1876
+ }
1877
+ }
1878
+ async handleExportSchema(args) {
1879
+ try {
1880
+ const patternId = argStringRequired(args, "patternId");
1881
+ const pattern = this.getEngine().getPattern(patternId);
1882
+ if (!pattern) return { schema: `// Error: pattern '${patternId}' not found` };
1883
+ return { schema: this.getEngine().exportProto(pattern) };
1884
+ } catch (error) {
1885
+ return { schema: `// Error: ${this.errorMessage(error)}` };
1886
+ }
1887
+ }
1888
+ async handleInferStateMachine(args) {
1889
+ try {
1890
+ const rawMessages = args.messages;
1891
+ if (!Array.isArray(rawMessages)) throw new Error("messages must be an array");
1892
+ const hasLegacyShape = rawMessages.some((message) => isRecord$1(message) && (message.direction === "in" || message.direction === "out"));
1893
+ let stateMachine;
1894
+ if (hasLegacyShape) {
1895
+ const legacyMessages = rawMessages.map((message, index) => {
1896
+ if (!isRecord$1(message)) throw new Error(`messages[${index}] must be an object`);
1897
+ const direction = message.direction;
1898
+ const payloadHex = typeof message.payloadHex === "string" ? message.payloadHex : "";
1899
+ const timestamp = typeof message.timestamp === "number" ? message.timestamp : void 0;
1900
+ const payload = Buffer.from(payloadHex.replace(/\s+/g, ""), "hex");
1901
+ if (direction !== "in" && direction !== "out") throw new Error(`messages[${index}].direction must be "in" or "out"`);
1902
+ return {
1903
+ direction,
1904
+ payload,
1905
+ ...timestamp !== void 0 ? { timestamp } : {}
1906
+ };
1907
+ });
1908
+ stateMachine = this.getInferrer().inferStateMachine(legacyMessages);
1909
+ } else {
1910
+ const messages = rawMessages.map((message, index) => parseProtocolMessage(message, index));
1911
+ stateMachine = this.getInferrer().infer(messages);
1912
+ }
1913
+ if (args.simplify === true) stateMachine = this.getInferrer().simplify(stateMachine);
1914
+ return {
1915
+ stateMachine,
1916
+ mermaid: this.getInferrer().generateMermaid(stateMachine),
1917
+ success: true
1918
+ };
1919
+ } catch (error) {
1920
+ return {
1921
+ stateMachine: { ...EMPTY_STATE_MACHINE },
1922
+ success: false,
1923
+ error: this.errorMessage(error)
1924
+ };
1925
+ }
1926
+ }
1927
+ async handleVisualizeState(args) {
1928
+ try {
1929
+ const stateMachineValue = args.stateMachine;
1930
+ if (!isRecord$1(stateMachineValue)) return { mermaidDiagram: this.getInferrer().generateMermaid(EMPTY_STATE_MACHINE) };
1931
+ const states = Array.isArray(stateMachineValue.states) ? stateMachineValue.states : [];
1932
+ const transitions = Array.isArray(stateMachineValue.transitions) ? stateMachineValue.transitions : [];
1933
+ const initialState = typeof stateMachineValue.initialState === "string" ? stateMachineValue.initialState : "";
1934
+ const finalStates = Array.isArray(stateMachineValue.finalStates) ? stateMachineValue.finalStates.filter((state) => typeof state === "string") : [];
1935
+ return { mermaidDiagram: this.getInferrer().generateMermaid({
1936
+ states: states.filter((state) => isRecord$1(state)),
1937
+ transitions: transitions.filter((transition) => isRecord$1(transition)),
1938
+ initial: initialState,
1939
+ initialState,
1940
+ finalStates
1941
+ }) };
1942
+ } catch (error) {
1943
+ return { mermaidDiagram: `stateDiagram-v2\n note right of empty: ${this.errorMessage(error)}` };
1944
+ }
1945
+ }
1946
+ };
1947
+ //#endregion
1948
+ //#region src/server/domains/protocol-analysis/handlers/payload-handlers.ts
1949
+ var ProtocolAnalysisPayloadHandlers = class extends ProtocolAnalysisPatternHandlers {
1950
+ async handlePayloadTemplateBuild(args) {
1951
+ try {
1952
+ const rawFields = args.fields;
1953
+ if (!Array.isArray(rawFields)) throw new Error("fields must be an array");
1954
+ const { payload, segments } = buildPayloadFromTemplate(rawFields.map((field, index) => parsePayloadTemplateField(field, index)), parseEndian(args.endian));
1955
+ this.emitEvent("protocol:payload_built", {
1956
+ byteLength: payload.length,
1957
+ fieldCount: segments.length
1958
+ });
1959
+ return {
1960
+ hexPayload: payload.toString("hex"),
1961
+ byteLength: payload.length,
1962
+ fields: segments,
1963
+ success: true
1964
+ };
1965
+ } catch (error) {
1966
+ return {
1967
+ hexPayload: "",
1968
+ byteLength: 0,
1969
+ fields: [],
1970
+ success: false,
1971
+ error: this.errorMessage(error)
1972
+ };
1973
+ }
1974
+ }
1975
+ async handlePayloadMutate(args) {
1976
+ let originalHex = "";
1977
+ try {
1978
+ if (typeof args.hexPayload !== "string") throw new Error("hexPayload must be a string");
1979
+ originalHex = normalizeHexString(args.hexPayload, "hexPayload");
1980
+ const rawMutations = args.mutations;
1981
+ if (!Array.isArray(rawMutations)) throw new Error("mutations must be an array");
1982
+ let payload = Buffer.from(originalHex, "hex");
1983
+ const appliedMutations = [];
1984
+ for (const [index, rawMutation] of rawMutations.entries()) {
1985
+ const mutation = parsePayloadMutation(rawMutation, index);
1986
+ const result = applyPayloadMutation(payload, mutation, index);
1987
+ payload = result.payload;
1988
+ appliedMutations.push(result.summary);
1989
+ }
1990
+ this.emitEvent("protocol:payload_mutated", {
1991
+ byteLength: payload.length,
1992
+ mutationCount: appliedMutations.length
1993
+ });
1994
+ return {
1995
+ originalHex,
1996
+ mutatedHex: payload.toString("hex"),
1997
+ byteLength: payload.length,
1998
+ appliedMutations,
1999
+ success: true
2000
+ };
2001
+ } catch (error) {
2002
+ return {
2003
+ originalHex,
2004
+ mutatedHex: "",
2005
+ byteLength: 0,
2006
+ appliedMutations: [],
2007
+ success: false,
2008
+ error: this.errorMessage(error)
2009
+ };
2010
+ }
2011
+ }
2012
+ };
2013
+ //#endregion
2014
+ //#region src/server/domains/protocol-analysis/handlers/link-layer-handlers.ts
2015
+ var ProtocolAnalysisLinkLayerHandlers = class extends ProtocolAnalysisPayloadHandlers {
2016
+ async handleEthernetFrameBuild(args) {
2017
+ try {
2018
+ const destinationMac = parseMacAddress(args.destinationMac, "destinationMac");
2019
+ const sourceMac = parseMacAddress(args.sourceMac, "sourceMac");
2020
+ const etherType = parseEtherType(args.etherType, "etherType");
2021
+ const frame = buildEthernetFrame(destinationMac, sourceMac, etherType, parseHexPayload$1(args.payloadHex, "payloadHex"));
2022
+ this.emitEvent("protocol:ethernet_frame_built", {
2023
+ byteLength: frame.length,
2024
+ etherType: `0x${etherType.toString(16).padStart(4, "0")}`
2025
+ });
2026
+ return {
2027
+ destinationMac: destinationMac.canonical,
2028
+ sourceMac: sourceMac.canonical,
2029
+ etherType,
2030
+ etherTypeHex: `0x${etherType.toString(16).padStart(4, "0")}`,
2031
+ byteLength: frame.length,
2032
+ headerHex: frame.subarray(0, 14).toString("hex"),
2033
+ frameHex: frame.toString("hex"),
2034
+ success: true
2035
+ };
2036
+ } catch (error) {
2037
+ return {
2038
+ destinationMac: "",
2039
+ sourceMac: "",
2040
+ etherType: 0,
2041
+ etherTypeHex: "0x0000",
2042
+ byteLength: 0,
2043
+ headerHex: "",
2044
+ frameHex: "",
2045
+ success: false,
2046
+ error: this.errorMessage(error)
2047
+ };
2048
+ }
2049
+ }
2050
+ async handleArpBuild(args) {
2051
+ try {
2052
+ const operation = args.operation === "reply" ? "reply" : "request";
2053
+ const senderMac = parseMacAddress(args.senderMac, "senderMac");
2054
+ const targetMac = parseMacAddress(args.targetMac ?? "00:00:00:00:00:00", "targetMac");
2055
+ const senderIp = parseIpv4Address(args.senderIp, "senderIp");
2056
+ const targetIp = parseIpv4Address(args.targetIp, "targetIp");
2057
+ const payload = buildArpPayload({
2058
+ operation,
2059
+ hardwareType: args.hardwareType === void 0 ? 1 : parseNonNegativeInteger(args.hardwareType, "hardwareType"),
2060
+ protocolType: parseEtherType(args.protocolType ?? "ipv4", "protocolType"),
2061
+ hardwareSize: args.hardwareSize === void 0 ? 6 : parsePositiveInteger(args.hardwareSize, "hardwareSize"),
2062
+ protocolSize: args.protocolSize === void 0 ? 4 : parsePositiveInteger(args.protocolSize, "protocolSize"),
2063
+ senderMac,
2064
+ senderIp,
2065
+ targetMac,
2066
+ targetIp
2067
+ });
2068
+ this.emitEvent("protocol:arp_built", {
2069
+ operation,
2070
+ byteLength: payload.length
2071
+ });
2072
+ return {
2073
+ operation,
2074
+ byteLength: payload.length,
2075
+ payloadHex: payload.toString("hex"),
2076
+ senderMac: senderMac.canonical,
2077
+ senderIp: args.senderIp,
2078
+ targetMac: targetMac.canonical,
2079
+ targetIp: args.targetIp,
2080
+ success: true
2081
+ };
2082
+ } catch (error) {
2083
+ return {
2084
+ operation: null,
2085
+ byteLength: 0,
2086
+ payloadHex: "",
2087
+ senderMac: "",
2088
+ senderIp: "",
2089
+ targetMac: "",
2090
+ targetIp: "",
2091
+ success: false,
2092
+ error: this.errorMessage(error)
2093
+ };
2094
+ }
2095
+ }
2096
+ };
2097
+ //#endregion
2098
+ //#region src/server/domains/protocol-analysis/handlers/ip-packet-handlers.ts
2099
+ var ProtocolAnalysisIpPacketHandlers = class extends ProtocolAnalysisLinkLayerHandlers {
2100
+ async handleRawIpPacketBuild(args) {
2101
+ try {
2102
+ const version = args.version === "ipv6" ? "ipv6" : "ipv4";
2103
+ const payload = parseHexPayload$1(args.payloadHex ?? "", "payloadHex");
2104
+ const protocol = parseIpProtocol(args.protocol, "protocol");
2105
+ const dscp = args.dscp === void 0 ? 0 : parseNonNegativeInteger(args.dscp, "dscp");
2106
+ const ecn = args.ecn === void 0 ? 0 : parseNonNegativeInteger(args.ecn, "ecn");
2107
+ if (dscp > 63) throw new Error("dscp must be between 0 and 63");
2108
+ if (ecn > 3) throw new Error("ecn must be between 0 and 3");
2109
+ if (version === "ipv4") {
2110
+ const ttl = args.ttl === void 0 ? 64 : parseByte(args.ttl, "ttl");
2111
+ const identification = args.identification === void 0 ? 0 : parseNonNegativeInteger(args.identification, "identification");
2112
+ const fragmentOffset = args.fragmentOffset === void 0 ? 0 : parseNonNegativeInteger(args.fragmentOffset, "fragmentOffset");
2113
+ if (identification > 65535) throw new Error("identification must be between 0 and 65535");
2114
+ if (fragmentOffset > 8191) throw new Error("fragmentOffset must be between 0 and 8191");
2115
+ const { packet, checksum } = buildIpv4Packet({
2116
+ sourceIp: parseIpAddress(args.sourceIp, "ipv4", "sourceIp"),
2117
+ destinationIp: parseIpAddress(args.destinationIp, "ipv4", "destinationIp"),
2118
+ protocol,
2119
+ payload,
2120
+ ttl,
2121
+ identification,
2122
+ dontFragment: args.dontFragment === true,
2123
+ moreFragments: args.moreFragments === true,
2124
+ fragmentOffset,
2125
+ dscp,
2126
+ ecn
2127
+ });
2128
+ this.emitEvent("protocol:ip_packet_built", {
2129
+ version,
2130
+ protocol,
2131
+ byteLength: packet.length
2132
+ });
2133
+ return {
2134
+ version,
2135
+ protocol,
2136
+ byteLength: packet.length,
2137
+ headerLength: 20,
2138
+ packetHex: packet.toString("hex"),
2139
+ headerHex: packet.subarray(0, 20).toString("hex"),
2140
+ payloadHex: payload.toString("hex"),
2141
+ checksumHex: checksum.toString(16).padStart(4, "0"),
2142
+ success: true
2143
+ };
2144
+ }
2145
+ const hopLimit = args.hopLimit === void 0 ? args.ttl === void 0 ? 64 : parseByte(args.ttl, "ttl") : parseByte(args.hopLimit, "hopLimit");
2146
+ const flowLabel = args.flowLabel === void 0 ? 0 : parseNonNegativeInteger(args.flowLabel, "flowLabel");
2147
+ if (flowLabel > 1048575) throw new Error("flowLabel must be between 0 and 1048575");
2148
+ const packet = buildIpv6Packet({
2149
+ sourceIp: parseIpAddress(args.sourceIp, "ipv6", "sourceIp"),
2150
+ destinationIp: parseIpAddress(args.destinationIp, "ipv6", "destinationIp"),
2151
+ protocol,
2152
+ payload,
2153
+ hopLimit,
2154
+ dscp,
2155
+ ecn,
2156
+ flowLabel
2157
+ });
2158
+ this.emitEvent("protocol:ip_packet_built", {
2159
+ version,
2160
+ protocol,
2161
+ byteLength: packet.length
2162
+ });
2163
+ return {
2164
+ version,
2165
+ protocol,
2166
+ byteLength: packet.length,
2167
+ headerLength: 40,
2168
+ packetHex: packet.toString("hex"),
2169
+ headerHex: packet.subarray(0, 40).toString("hex"),
2170
+ payloadHex: payload.toString("hex"),
2171
+ checksumHex: null,
2172
+ success: true
2173
+ };
2174
+ } catch (error) {
2175
+ return {
2176
+ version: null,
2177
+ protocol: null,
2178
+ byteLength: 0,
2179
+ headerLength: 0,
2180
+ packetHex: "",
2181
+ headerHex: "",
2182
+ payloadHex: "",
2183
+ checksumHex: null,
2184
+ success: false,
2185
+ error: this.errorMessage(error)
2186
+ };
2187
+ }
2188
+ }
2189
+ async handleIcmpEchoBuild(args) {
2190
+ try {
2191
+ const operation = args.operation === "reply" ? "reply" : "request";
2192
+ const identifier = args.identifier === void 0 ? 0 : parseNonNegativeInteger(args.identifier, "identifier");
2193
+ const sequenceNumber = args.sequenceNumber === void 0 ? 0 : parseNonNegativeInteger(args.sequenceNumber, "sequenceNumber");
2194
+ if (identifier > 65535) throw new Error("identifier must be between 0 and 65535");
2195
+ if (sequenceNumber > 65535) throw new Error("sequenceNumber must be between 0 and 65535");
2196
+ const payload = parseHexPayload$1(args.payloadHex ?? "", "payloadHex");
2197
+ const { packet, checksum } = buildIcmpEcho({
2198
+ operation,
2199
+ identifier,
2200
+ sequenceNumber,
2201
+ payload
2202
+ });
2203
+ const checksumHex = checksum.toString(16).padStart(4, "0");
2204
+ this.emitEvent("protocol:icmp_echo_built", {
2205
+ operation,
2206
+ byteLength: packet.length,
2207
+ checksumHex
2208
+ });
2209
+ return {
2210
+ operation,
2211
+ identifier,
2212
+ sequenceNumber,
2213
+ checksum,
2214
+ checksumHex,
2215
+ byteLength: packet.length,
2216
+ packetHex: packet.toString("hex"),
2217
+ payloadHex: payload.toString("hex"),
2218
+ success: true
2219
+ };
2220
+ } catch (error) {
2221
+ return {
2222
+ operation: null,
2223
+ identifier: null,
2224
+ sequenceNumber: null,
2225
+ checksum: null,
2226
+ checksumHex: "",
2227
+ byteLength: 0,
2228
+ packetHex: "",
2229
+ payloadHex: "",
2230
+ success: false,
2231
+ error: this.errorMessage(error)
2232
+ };
2233
+ }
2234
+ }
2235
+ };
2236
+ //#endregion
2237
+ //#region src/server/domains/protocol-analysis/handlers/checksum-handlers.ts
2238
+ var ProtocolAnalysisChecksumHandlers = class extends ProtocolAnalysisIpPacketHandlers {
2239
+ async handleChecksumApply(args) {
2240
+ try {
2241
+ const payload = parseHexPayload$1(args.hexPayload, "hexPayload");
2242
+ const rangeStart = args.startOffset === void 0 ? 0 : parseNonNegativeInteger(args.startOffset, "startOffset");
2243
+ const rangeEnd = args.endOffset === void 0 ? payload.length : parseNonNegativeInteger(args.endOffset, "endOffset");
2244
+ if (rangeStart > rangeEnd || rangeEnd > payload.length) throw new Error("checksum range must stay within the payload");
2245
+ const zeroOffset = args.zeroOffset === void 0 ? void 0 : parseNonNegativeInteger(args.zeroOffset, "zeroOffset");
2246
+ const zeroLength = args.zeroLength === void 0 ? 2 : parsePositiveInteger(args.zeroLength, "zeroLength");
2247
+ const writeOffset = args.writeOffset === void 0 ? zeroOffset : parseNonNegativeInteger(args.writeOffset, "writeOffset");
2248
+ const endian = parseChecksumEndian(args.endian);
2249
+ const working = Buffer.from(payload);
2250
+ if (zeroOffset !== void 0) {
2251
+ if (zeroOffset + zeroLength > working.length) throw new Error("zeroOffset and zeroLength must stay within the payload");
2252
+ working.fill(0, zeroOffset, zeroOffset + zeroLength);
2253
+ }
2254
+ const checksum = computeInternetChecksum(working.subarray(rangeStart, rangeEnd));
2255
+ if (writeOffset !== void 0) {
2256
+ if (writeOffset + 2 > working.length) throw new Error("writeOffset must leave room for a 16-bit checksum field");
2257
+ if (endian === "little") working.writeUInt16LE(checksum, writeOffset);
2258
+ else working.writeUInt16BE(checksum, writeOffset);
2259
+ }
2260
+ const checksumHex = checksum.toString(16).padStart(4, "0");
2261
+ this.emitEvent("protocol:checksum_applied", {
2262
+ checksumHex,
2263
+ byteLength: working.length
2264
+ });
2265
+ return {
2266
+ checksumHex,
2267
+ checksum,
2268
+ mutatedHex: working.toString("hex"),
2269
+ byteLength: working.length,
2270
+ rangeStart,
2271
+ rangeEnd,
2272
+ success: true
2273
+ };
2274
+ } catch (error) {
2275
+ return {
2276
+ checksumHex: "",
2277
+ checksum: 0,
2278
+ mutatedHex: "",
2279
+ byteLength: 0,
2280
+ rangeStart: 0,
2281
+ rangeEnd: 0,
2282
+ success: false,
2283
+ error: this.errorMessage(error)
2284
+ };
2285
+ }
2286
+ }
2287
+ };
2288
+ //#endregion
2289
+ //#region src/server/domains/protocol-analysis/handlers/packet-build-handlers.ts
2290
+ /**
2291
+ * ProtocolAnalysisPacketBuildHandlers — thin facade over packet build handlers.
2292
+ */
2293
+ var ProtocolAnalysisPacketBuildHandlers = class extends ProtocolAnalysisChecksumHandlers {};
2294
+ //#endregion
2295
+ //#region src/server/domains/protocol-analysis/handlers/pcap-handlers.ts
2296
+ var ProtocolAnalysisPcapHandlers = class extends ProtocolAnalysisPacketBuildHandlers {
2297
+ async handlePcapWrite(args) {
2298
+ try {
2299
+ const path = this.parseRequiredPath(args);
2300
+ if (!Array.isArray(args.packets)) throw new Error("packets must be an array");
2301
+ const packets = args.packets.map((entry, index) => parsePcapPacketInput(entry, index));
2302
+ const endianness = parsePacketEndianness(args.endianness);
2303
+ const timestampPrecision = parseTimestampPrecision(args.timestampPrecision);
2304
+ const snapLength = args.snapLength === void 0 ? 65535 : parsePositiveInteger(args.snapLength, "snapLength");
2305
+ const linkType = parsePcapLinkType(args.linkType ?? "ethernet", "linkType");
2306
+ const buffer = buildClassicPcap({
2307
+ packets,
2308
+ endianness,
2309
+ timestampPrecision,
2310
+ snapLength,
2311
+ linkType
2312
+ });
2313
+ await writeFile$1(path, buffer);
2314
+ this.emitEvent("protocol:pcap_written", {
2315
+ path,
2316
+ packetCount: packets.length,
2317
+ byteLength: buffer.length
2318
+ });
2319
+ return {
2320
+ path,
2321
+ packetCount: packets.length,
2322
+ byteLength: buffer.length,
2323
+ endianness,
2324
+ timestampPrecision,
2325
+ linkType,
2326
+ success: true
2327
+ };
2328
+ } catch (error) {
2329
+ return {
2330
+ path: typeof args.path === "string" ? args.path : "",
2331
+ packetCount: 0,
2332
+ byteLength: 0,
2333
+ endianness: null,
2334
+ timestampPrecision: null,
2335
+ linkType: null,
2336
+ success: false,
2337
+ error: this.errorMessage(error)
2338
+ };
2339
+ }
2340
+ }
2341
+ async handlePcapRead(args) {
2342
+ try {
2343
+ const path = this.parseRequiredPath(args);
2344
+ const maxPackets = args.maxPackets === void 0 ? void 0 : parsePositiveInteger(args.maxPackets, "maxPackets");
2345
+ const maxBytesPerPacket = args.maxBytesPerPacket === void 0 ? void 0 : parsePositiveInteger(args.maxBytesPerPacket, "maxBytesPerPacket");
2346
+ const { header, packets } = readClassicPcap(await readFile$1(path), maxPackets, maxBytesPerPacket);
2347
+ this.emitEvent("protocol:pcap_read", {
2348
+ path,
2349
+ packetCount: packets.length
2350
+ });
2351
+ return {
2352
+ path,
2353
+ header,
2354
+ packets,
2355
+ success: true
2356
+ };
2357
+ } catch (error) {
2358
+ return {
2359
+ path: typeof args.path === "string" ? args.path : "",
2360
+ header: null,
2361
+ packets: [],
2362
+ success: false,
2363
+ error: this.errorMessage(error)
2364
+ };
2365
+ }
2366
+ }
2367
+ parseRequiredPath(args) {
2368
+ if (typeof args.path !== "string" || args.path.trim().length === 0) throw new Error("path must be a non-empty string");
2369
+ return args.path;
2370
+ }
2371
+ };
2372
+ //#endregion
2373
+ //#region src/server/domains/protocol-analysis/handlers/packet-handlers.ts
2374
+ /**
2375
+ * ProtocolAnalysisPacketHandlers — thin facade over packet build + PCAP handlers.
2376
+ */
2377
+ var ProtocolAnalysisPacketHandlers = class extends ProtocolAnalysisPcapHandlers {};
2378
+ //#endregion
2379
+ //#region src/server/domains/protocol-analysis/handlers/fingerprint-handlers.ts
2380
+ /**
2381
+ * ProtocolAnalysisFingerprintHandlers — protocol fingerprint heuristics.
2382
+ */
2383
+ var ProtocolAnalysisFingerprintHandlers = class extends ProtocolAnalysisPacketHandlers {
2384
+ async handleProtoFingerprint(args) {
2385
+ const hexPayloads = argStringArray(args, "hexPayloads");
2386
+ const includeKnown = args.includeKnownProtocols !== false;
2387
+ const includeHints = args.includeFieldHints !== false;
2388
+ if (hexPayloads.length === 0) return asJsonResponse({
2389
+ success: false,
2390
+ error: "hexPayloads is required"
2391
+ });
2392
+ return asJsonResponse({
2393
+ success: true,
2394
+ fingerprints: hexPayloads.map((hex, index) => {
2395
+ const clean = hex.replace(/\s/g, "");
2396
+ const matches = [];
2397
+ const actualBytes = clean.length / 2;
2398
+ const tlsRecordLen = actualBytes >= 5 ? readU16(clean, 3) : -1;
2399
+ const hasCompleteTlsRecord = Number.isFinite(tlsRecordLen) && tlsRecordLen >= 0 && actualBytes >= 5 + tlsRecordLen;
2400
+ const isTlsClientHello = hasCompleteTlsRecord && tlsRecordLen >= PROTO_TLS_MIN_RECORD_LEN && readU8(clean, 0) === 22 && readU8(clean, 5) === 1;
2401
+ const isDns = isLikelyDnsHeader(clean);
2402
+ const isHttp = Object.keys(HTTP_METHODS).some((method) => clean.toUpperCase().startsWith(method));
2403
+ const isSsh = clean.toUpperCase().startsWith("5353482D");
2404
+ const isWs = clean.length >= 4 && (() => {
2405
+ const b0 = readU8(clean, 0);
2406
+ const b1 = readU8(clean, 1);
2407
+ const opcode = b0 & 15;
2408
+ if (opcode === 0) return false;
2409
+ const validOpcode = opcode <= 10 && !(opcode >= 3 && opcode <= 7);
2410
+ const masked = (b1 >> 7 & 1) === 1;
2411
+ const wsByteCount = clean.length / 2;
2412
+ let payloadLen = b1 & 127;
2413
+ let headerBytes = 2;
2414
+ if (payloadLen === 126) {
2415
+ if (wsByteCount < 4) return false;
2416
+ payloadLen = readU16(clean, 2);
2417
+ headerBytes = 4;
2418
+ } else if (payloadLen === 127) {
2419
+ if (wsByteCount < 10) return false;
2420
+ const hi32 = readU16(clean, 2) << 16 | readU16(clean, 4);
2421
+ const lo32 = readU16(clean, 6) << 16 | readU16(clean, 8);
2422
+ payloadLen = hi32 > 0 ? 4294967295 : lo32;
2423
+ headerBytes = 10;
2424
+ }
2425
+ return validOpcode && wsByteCount >= headerBytes + (masked ? 4 : 0) + payloadLen;
2426
+ })();
2427
+ let deepParse = null;
2428
+ if (isTlsClientHello) {
2429
+ matches.push({
2430
+ protocol: "TLS ClientHello",
2431
+ layer: "L6-TLS",
2432
+ confidence: PROTO_TLS_CONFIDENCE
2433
+ });
2434
+ if (includeHints) deepParse = parseTlsClientHello(clean);
2435
+ } else if (isHttp) {
2436
+ matches.push({
2437
+ protocol: "HTTP/1.x",
2438
+ layer: "L7-HTTP",
2439
+ confidence: PROTO_HTTP_CONFIDENCE
2440
+ });
2441
+ if (includeHints) deepParse = {
2442
+ method: Object.entries(HTTP_METHODS).find(([prefix]) => clean.toUpperCase().startsWith(prefix))?.[1] ?? "UNKNOWN",
2443
+ httpVersion: clean.indexOf("2048545450") > 0 ? "1.x" : "unknown"
2444
+ };
2445
+ } else if (isSsh) {
2446
+ matches.push({
2447
+ protocol: "SSH",
2448
+ layer: "L7-SSH",
2449
+ confidence: PROTO_SSH_CONFIDENCE
2450
+ });
2451
+ if (includeHints && clean.length >= 20) deepParse = { banner: Buffer.from(clean.substring(0, Math.min(clean.length, 80)), "hex").toString("ascii") };
2452
+ } else if (isWs) {
2453
+ matches.push({
2454
+ protocol: "WebSocket",
2455
+ layer: "L7-WS",
2456
+ confidence: PROTO_WS_CONFIDENCE
2457
+ });
2458
+ if (includeHints && clean.length >= 4) {
2459
+ const b0 = readU8(clean, 0);
2460
+ const b1 = readU8(clean, 1);
2461
+ const opcode = b0 & 15;
2462
+ const masked = b1 >> 7 & 1;
2463
+ let payloadLen = b1 & 127;
2464
+ let headerSize = 2;
2465
+ if (payloadLen === 126) {
2466
+ payloadLen = clean.length >= 4 ? readU16(clean, 2) : 0;
2467
+ headerSize = 4;
2468
+ } else if (payloadLen === 127) {
2469
+ if (clean.length >= 20) {
2470
+ const hi32 = readU16(clean, 2) << 16 | readU16(clean, 4);
2471
+ const lo32 = readU16(clean, 6) << 16 | readU16(clean, 8);
2472
+ payloadLen = hi32 > 0 ? 4294967295 : lo32;
2473
+ } else payloadLen = 0;
2474
+ headerSize = 10;
2475
+ }
2476
+ if (masked) headerSize += 4;
2477
+ deepParse = {
2478
+ fin: b0 >> 7 & 1,
2479
+ rsv1: b0 >> 6 & 1,
2480
+ opcode,
2481
+ opcodeName: WS_OPCODES[opcode] ?? `reserved(${opcode})`,
2482
+ masked: !!masked,
2483
+ payloadLength: payloadLen,
2484
+ headerSize
2485
+ };
2486
+ }
2487
+ } else if (isDns) {
2488
+ matches.push({
2489
+ protocol: "DNS",
2490
+ layer: "L7-DNS",
2491
+ confidence: .85
2492
+ });
2493
+ if (includeHints) deepParse = parseDnsHeader(clean);
2494
+ }
2495
+ if (includeKnown && matches.length === 0) {
2496
+ if (hasCompleteTlsRecord && /^160301|^160302|^160303/i.test(clean.substring(0, 8))) matches.push({
2497
+ protocol: "TLS Record",
2498
+ layer: "L6-TLS",
2499
+ confidence: .9
2500
+ });
2501
+ if (clean.substring(0, 8).startsWith("50524920")) matches.push({
2502
+ protocol: "HTTP/2 PRI",
2503
+ layer: "L7-HTTP2",
2504
+ confidence: .9
2505
+ });
2506
+ }
2507
+ const fieldHints = [];
2508
+ if (includeHints && !deepParse && clean.length >= 8) {
2509
+ const first2 = readU16(clean, 0);
2510
+ if (first2 > 0 && first2 < clean.length / 2) fieldHints.push({
2511
+ offset: 0,
2512
+ hint: `possible length field (${first2} bytes)`
2513
+ });
2514
+ }
2515
+ return {
2516
+ index,
2517
+ size: actualBytes,
2518
+ protocolMatches: matches.length > 0 ? matches : [{
2519
+ protocol: "unknown",
2520
+ layer: "unknown",
2521
+ confidence: 0
2522
+ }],
2523
+ ...deepParse ? { parsedFields: deepParse } : {},
2524
+ ...fieldHints.length > 0 ? { fieldHints } : {}
2525
+ };
2526
+ })
2527
+ });
2528
+ }
2529
+ };
2530
+ //#endregion
2531
+ //#region src/server/domains/protocol-analysis/handlers/handler-class.ts
2532
+ /**
2533
+ * ProtocolAnalysisHandlers — thin facade over the split handler chain.
2534
+ */
2535
+ var ProtocolAnalysisHandlers = class extends ProtocolAnalysisFingerprintHandlers {};
2536
+ //#endregion
2537
+ export { ProtocolAnalysisHandlers };