@jshookmcp/jshook 0.2.9 → 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 (187) hide show
  1. package/README.md +2 -2
  2. package/README.zh.md +2 -2
  3. package/dist/{AntiCheatDetector-BNk-EoBt.mjs → AntiCheatDetector-CqGDXmfc.mjs} +159 -53
  4. package/dist/{CodeInjector-Cq8q01kp.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
  5. package/dist/{ConsoleMonitor-CPVQW1Y-.mjs → ConsoleMonitor-DykL3IAw.mjs} +85 -17
  6. package/dist/{DetailedDataManager-BQQcxh64.mjs → DetailedDataManager-HT49OrvF.mjs} +1 -1
  7. package/dist/{ExtensionManager-CWYgw0YW.mjs → ExtensionManager-BDMsY2Dz.mjs} +15 -8
  8. package/dist/{HardwareBreakpoint-B9gZCdFP.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
  9. package/dist/{HeapAnalyzer-BLDH0dCv.mjs → HeapAnalyzer-DruMgsgj.mjs} +20 -20
  10. package/dist/{HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs → HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs} +1 -74
  11. package/dist/{InstrumentationSession-CvPC7Jwy.mjs → InstrumentationSession-DLH0vd-z.mjs} +2 -2
  12. package/dist/{MemoryController-CbVdCIJF.mjs → MemoryController-CMtviNW_.mjs} +3 -3
  13. package/dist/{MemoryScanSession-BsDZbLYm.mjs → MemoryScanSession-ITgb_NMi.mjs} +2 -2
  14. package/dist/{MemoryScanner-Bcpml6II.mjs → MemoryScanner-CiL7Z3ey.mjs} +12 -9
  15. package/dist/{NativeMemoryManager.impl-dZtA1ZGn.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +13 -10
  16. package/dist/{NativeMemoryManager.utils-B-FjA2mJ.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
  17. package/dist/{PEAnalyzer-D1lzJ_VG.mjs → PEAnalyzer-DMQ44gen.mjs} +15 -15
  18. package/dist/{PageController-Bqm2kZ_X.mjs → PageController-BPJNqqBN.mjs} +18 -4
  19. package/dist/{PointerChainEngine-BOhyVsjx.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
  20. package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
  21. package/dist/{ResponseBuilder-D3iFYx2N.mjs → ResponseBuilder-CJXWmWNw.mjs} +10 -10
  22. package/dist/{ScriptManager-aHHq0X7U.mjs → ScriptManager-ZuWD-0Jg.mjs} +195 -192
  23. package/dist/{Speedhack-CqdIFlQl.mjs → Speedhack-D-z0umeT.mjs} +2 -2
  24. package/dist/{StructureAnalyzer-DhFaPvRO.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
  25. package/dist/{ToolCatalog-C0JGZoOm.mjs → ToolCatalog-5OJdMiF0.mjs} +81 -81
  26. package/dist/{ToolProbe-oC7aPrkv.mjs → ToolProbe-DbCFGyrg.mjs} +1 -1
  27. package/dist/{ToolRegistry-BjaF4oNz.mjs → ToolRegistry-B9krbTtI.mjs} +51 -2
  28. package/dist/{ToolRouter.policy-BWV67ZK-.mjs → ToolRouter.policy-BGDAGyeH.mjs} +60 -20
  29. package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
  30. package/dist/{Win32API-CePkipZY.mjs → Win32API-C2kjj0ze.mjs} +18 -12
  31. package/dist/{Win32Debug-BvKs-gxc.mjs → Win32Debug-CKrGOTpo.mjs} +2 -2
  32. package/dist/{WorkflowEngine-CuvkZtWu.mjs → WorkflowEngine-DJ6M4opp.mjs} +226 -255
  33. package/dist/analysis-BHeJW2Nb.mjs +1234 -0
  34. package/dist/{antidebug-CqDTB_uk.mjs → antidebug-BRKeyt27.mjs} +3 -3
  35. package/dist/{artifactRetention-CFEprwPw.mjs → artifactRetention-CPXkUJXp.mjs} +13 -6
  36. package/dist/{artifacts-Bk2-_uPq.mjs → artifacts-DkfosXH3.mjs} +1 -1
  37. package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
  38. package/dist/{binary-instrument-CXfpx6fT.mjs → binary-instrument--V3MAhJ4.mjs} +19 -27
  39. package/dist/bind-helpers-ClV34xdn.mjs +42 -0
  40. package/dist/{boringssl-inspector-BH2D3VKc.mjs → boringssl-inspector-Bo_LOLaS.mjs} +1 -1
  41. package/dist/{browser-BpOr5PEx.mjs → browser-Dx3_S2cG.mjs} +324 -37
  42. package/dist/capabilities-CcHlvWgK.mjs +33 -0
  43. package/dist/{constants-B0OANIBL.mjs → constants-CDZLOoVv.mjs} +18 -3
  44. package/dist/{coordination-qUbyF8KU.mjs → coordination-DgItD9DL.mjs} +2 -2
  45. package/dist/{debugger-gnKxRSN0.mjs → debugger-RS3RSAqs.mjs} +30 -13
  46. package/dist/definitions-BEoYofW5.mjs +47 -0
  47. package/dist/{definitions-bAhHQJq9.mjs → definitions-BRaefg3u.mjs} +11 -5
  48. package/dist/{definitions-DVGfrn7y.mjs → definitions-BbkvZkiv.mjs} +2 -2
  49. package/dist/definitions-BtWSHJ3o.mjs +17 -0
  50. package/dist/{definitions-BMfYXoNC.mjs → definitions-C1gCHO0i.mjs} +1 -1
  51. package/dist/{definitions-C1UvM5Iy.mjs → definitions-CDOg_b-l.mjs} +14 -2
  52. package/dist/definitions-CVPD9hzZ.mjs +54 -0
  53. package/dist/{definitions-Cke7zEb8.mjs → definitions-Cea8Lgl7.mjs} +1 -1
  54. package/dist/definitions-DAgIyjxM.mjs +10 -0
  55. package/dist/{definitions-B4rAvHNZ.mjs → definitions-DJA27nsL.mjs} +12 -9
  56. package/dist/{definitions-ClJLzsJQ.mjs → definitions-DKPFU3LW.mjs} +1 -1
  57. package/dist/{definitions-D3VsGcvz.mjs → definitions-DPRpZQ96.mjs} +7 -7
  58. package/dist/{definitions-B18eyf0B.mjs → definitions-DUE5gmdn.mjs} +1 -1
  59. package/dist/definitions-DYVjOtxa.mjs +26 -0
  60. package/dist/{definitions-BB_4jnmy.mjs → definitions-DcYLVLCo.mjs} +1 -1
  61. package/dist/{definitions-Beid2EB3.mjs → definitions-Pp5LI2H4.mjs} +1 -1
  62. package/dist/definitions-j9KdHVNR.mjs +14 -0
  63. package/dist/definitions-uzkjBwa7.mjs +258 -0
  64. package/dist/{definitions-Cq-zroAU.mjs → definitions-va-AnLuQ.mjs} +4 -4
  65. package/dist/{encoding-Bvz5jLRv.mjs → encoding-DJeqHmpd.mjs} +18 -4
  66. package/dist/{evidence-graph-bridge-C_fv9PuC.mjs → evidence-graph-bridge-DcYizFk2.mjs} +1 -0
  67. package/dist/{factory-DxlGh9Xf.mjs → factory-C90tBff6.mjs} +6 -6
  68. package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
  69. package/dist/{graphql-DYWzJ29s.mjs → graphql-CoHrhweh.mjs} +205 -34
  70. package/dist/{handlers-C67ktuRN.mjs → handlers-4jmR0nMs.mjs} +220 -32
  71. package/dist/{handlers-DlCJN4Td.mjs → handlers-BAHPxcch.mjs} +122 -90
  72. package/dist/{handlers-9sAbfIg-.mjs → handlers-BOs9b907.mjs} +849 -801
  73. package/dist/{handlers-DxGIq15_2.mjs → handlers-BWXEy6ef.mjs} +16 -16
  74. package/dist/{handlers-tB9Mp9ZK.mjs → handlers-Bndn6QvE.mjs} +31 -4
  75. package/dist/{handlers-CTsDAO6p.mjs → handlers-BqC4bD4s.mjs} +1 -1
  76. package/dist/{handlers-C87g8oCe.mjs → handlers-BtYq60bM2.mjs} +1 -1
  77. package/dist/{handlers-DeLOCd5m.mjs → handlers-BzgcB4iv.mjs} +17 -17
  78. package/dist/{handlers-Cgyg6c0U.mjs → handlers-CRyRWj2b.mjs} +237 -23
  79. package/dist/{handlers-U6L4xhuF.mjs → handlers-CVv2H1uq.mjs} +24 -17
  80. package/dist/{handlers-tiy7EIBp.mjs → handlers-Dl5a7JS4.mjs} +3 -3
  81. package/dist/{handlers-D6j6yka7.mjs → handlers-Dx2d7jt7.mjs} +1893 -1480
  82. package/dist/{handlers-Bl8zkwz1.mjs → handlers-Dz9PYsCa.mjs} +95 -6
  83. package/dist/handlers-HujRKC3b.mjs +661 -0
  84. package/dist/{handlers.impl-DS0d9fUw.mjs → handlers.impl-XWXkQfyi.mjs} +70 -24
  85. package/dist/{hooks-CzCWByww.mjs → hooks-B1B8NRHL.mjs} +3 -3
  86. package/dist/index.mjs +154 -144
  87. package/dist/{maintenance-P7ePRXQC.mjs → maintenance-PRMkLVRW.mjs} +35 -30
  88. package/dist/manifest-67Bok-Si.mjs +58 -0
  89. package/dist/{manifest-B3QVVeBS.mjs → manifest-6lNTMZAB2.mjs} +33 -28
  90. package/dist/manifest-B2duEHiH.mjs +90 -0
  91. package/dist/manifest-B6EY9Vm8.mjs +57 -0
  92. package/dist/{manifest-gZ4s_UtG.mjs → manifest-B6nKSbyY.mjs} +32 -33
  93. package/dist/{manifest-2ToTpjv8.mjs → manifest-BL8AQNPF.mjs} +31 -31
  94. package/dist/{manifest-DzwvxPJX.mjs → manifest-BSZvJJmV.mjs} +23 -14
  95. package/dist/{manifest-Sc_0JQ13.mjs → manifest-BU7qzUyX.mjs} +23 -23
  96. package/dist/{manifest-CT7zZBV1.mjs → manifest-Bl62e8WK.mjs} +24 -23
  97. package/dist/manifest-Bo5cXjdt.mjs +82 -0
  98. package/dist/manifest-BpS4gtUK.mjs +1347 -0
  99. package/dist/manifest-Bv65_e2W.mjs +101 -0
  100. package/dist/manifest-BytNIF4Z.mjs +117 -0
  101. package/dist/{manifest-BqrQ4Tpj.mjs → manifest-C-xtsjS3.mjs} +23 -23
  102. package/dist/{manifest-NXctwWQq.mjs → manifest-CDYl7OhA.mjs} +36 -38
  103. package/dist/manifest-CRZ3xmkD.mjs +61 -0
  104. package/dist/manifest-CoW6u4Tp.mjs +132 -0
  105. package/dist/manifest-Cq5zN_8A.mjs +50 -0
  106. package/dist/{manifest-CAhOuvSl.mjs → manifest-D7YZM_2e.mjs} +75 -85
  107. package/dist/{manifest-DCyjf4n2.mjs → manifest-DE_VrAeQ.mjs} +27 -7
  108. package/dist/manifest-DGsXSCpT.mjs +39 -0
  109. package/dist/{manifest-BB2J8IMJ.mjs → manifest-DJ2vfEuW.mjs} +48 -41
  110. package/dist/{manifest-3g71z6Bg.mjs → manifest-DPXDYhEu.mjs} +26 -25
  111. package/dist/manifest-Dd4fQb0a.mjs +322 -0
  112. package/dist/{manifest-CXsRWjjI.mjs → manifest-Deq6opGg.mjs} +95 -96
  113. package/dist/{manifest-C9RT5nk32.mjs → manifest-DfJTafJK.mjs} +14 -11
  114. package/dist/manifest-DgOdgN_j.mjs +50 -0
  115. package/dist/{manifest-BmtZzQiQ2.mjs → manifest-DlbMW4v4.mjs} +17 -15
  116. package/dist/{manifest-DrbmZcFl2.mjs → manifest-DmVfbH0w.mjs} +212 -91
  117. package/dist/manifest-Dog6Ddjr.mjs +109 -0
  118. package/dist/manifest-DvgU5FWb.mjs +58 -0
  119. package/dist/manifest-HsfDBs7j.mjs +50 -0
  120. package/dist/manifest-I8oQHvCG.mjs +186 -0
  121. package/dist/manifest-NvH_a-av.mjs +786 -0
  122. package/dist/{manifest-Dh8WBmEW.mjs → manifest-cEJU1v0Z.mjs} +24 -24
  123. package/dist/manifest-wOl5XLB12.mjs +112 -0
  124. package/dist/{modules-C184v-S9.mjs → modules-tZozf0LQ.mjs} +130 -860
  125. package/dist/{mojo-ipc-B_H61Afw.mjs → mojo-ipc-DXNEXEqb.mjs} +141 -26
  126. package/dist/{network-671Cw6hV.mjs → network-CPVvwvFg.mjs} +1329 -823
  127. package/dist/{outputPaths-B1uGmrWZ.mjs → outputPaths-um7lCRY3.mjs} +4 -8
  128. package/dist/{platform-WmNn8Sxb.mjs → platform-CYeFoTWp.mjs} +101 -10
  129. package/dist/{process-QcbIy5Zq.mjs → process-BTbgcVc6.mjs} +251 -346
  130. package/dist/{proxy-DqNs0bAd.mjs → proxy-r8YN6nP1.mjs} +30 -8
  131. package/dist/{registry-D-6e18lB.mjs → registry-Bl8ZQW61.mjs} +3 -3
  132. package/dist/{response-BQVP-xUn.mjs → response-CWhh2aLo.mjs} +7 -1
  133. package/dist/{shared-state-board-DV-dpHFJ.mjs → shared-state-board-BoZnSoj-.mjs} +2 -2
  134. package/dist/{sourcemap-Dq8ez8vS.mjs → sourcemap-BIDHUVXy.mjs} +350 -66
  135. package/dist/{streaming-BUQ0VJsg.mjs → streaming-Dal6utPp.mjs} +13 -13
  136. package/dist/{tool-builder-DCbIC5Eo.mjs → tool-builder-BHJp32mV.mjs} +1 -1
  137. package/dist/{transform-CiYJfNX0.mjs → transform-DRVgGG90.mjs} +18 -14
  138. package/dist/wasm-BYx5UOeG.mjs +1044 -0
  139. package/dist/webcrack-Be0_FccV.mjs +747 -0
  140. package/dist/{workflow-f3xJOcjx.mjs → workflow-BpuKEtvn.mjs} +8 -8
  141. package/package.json +76 -43
  142. package/dist/TraceRecorder-DgxyVbdQ.mjs +0 -519
  143. package/dist/analysis-CL9uACt9.mjs +0 -463
  144. package/dist/bind-helpers-xFfRF-qm.mjs +0 -22
  145. package/dist/definitions-6M-eejaT.mjs +0 -53
  146. package/dist/definitions-B3QdlrHv.mjs +0 -34
  147. package/dist/definitions-CXEI7QC72.mjs +0 -216
  148. package/dist/definitions-C_4r7Fo-2.mjs +0 -14
  149. package/dist/definitions-CkFDALoa.mjs +0 -26
  150. package/dist/definitions-Cy3Sl6gV.mjs +0 -34
  151. package/dist/definitions-LKpC3-nL.mjs +0 -9
  152. package/dist/handlers-DdFzXLvF.mjs +0 -446
  153. package/dist/manifest-82baTv4U.mjs +0 -45
  154. package/dist/manifest-BKbgbSiY.mjs +0 -60
  155. package/dist/manifest-Bcf-TJzH.mjs +0 -848
  156. package/dist/manifest-Bnd7kqEY.mjs +0 -55
  157. package/dist/manifest-BqQX6OQC2.mjs +0 -65
  158. package/dist/manifest-Br4RPFt5.mjs +0 -370
  159. package/dist/manifest-C5qDjysN.mjs +0 -107
  160. package/dist/manifest-CBYWCUBJ.mjs +0 -51
  161. package/dist/manifest-CFADCRa1.mjs +0 -37
  162. package/dist/manifest-CQVhavRF.mjs +0 -114
  163. package/dist/manifest-CV12bcrF.mjs +0 -121
  164. package/dist/manifest-CZLUCfG02.mjs +0 -95
  165. package/dist/manifest-D6phHKFd.mjs +0 -131
  166. package/dist/manifest-DHsnKgP6.mjs +0 -60
  167. package/dist/manifest-Df_dliIe.mjs +0 -55
  168. package/dist/manifest-DhKRAT8_.mjs +0 -92
  169. package/dist/manifest-DlpTj4ic2.mjs +0 -193
  170. package/dist/manifest-DuwHjUa5.mjs +0 -70
  171. package/dist/manifest-qSleDqdO.mjs +0 -1023
  172. package/dist/wasm-DQTnHDs4.mjs +0 -531
  173. /package/dist/{CacheAdapters-CDe5WPSV.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
  174. /package/dist/{DarwinAPI-BNPxu0RH.mjs → DarwinAPI-ETyy0xyo.mjs} +0 -0
  175. /package/dist/{EventBus-DgPmwpeu.mjs → EventBus-DFKvADm3.mjs} +0 -0
  176. /package/dist/{EvidenceGraphBridge-SFesNera.mjs → EvidenceGraphBridge-318Oi0Lf.mjs} +0 -0
  177. /package/dist/{FingerprintManager-gzWtkKuf.mjs → FingerprintManager-BN4UQWnX.mjs} +0 -0
  178. /package/dist/{PrerequisiteError-Dl33Svkz.mjs → PrerequisiteError-TuyZIs6n.mjs} +0 -0
  179. /package/dist/{ReverseEvidenceGraph-Dlsk94LC.mjs → ReverseEvidenceGraph-C02-gXOh.mjs} +0 -0
  180. /package/dist/{StealthVerifier-Bo4T3bz8.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
  181. /package/dist/{VersionDetector-CwVLVdDM.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
  182. /package/dist/{betterSqlite3-0pqusHHH.mjs → betterSqlite3-DLSBZodi.mjs} +0 -0
  183. /package/dist/{concurrency-Bt0yv1kJ.mjs → concurrency-Drev_Vz9.mjs} +0 -0
  184. /package/dist/{formatAddress-DVkj9kpI.mjs → formatAddress-nnMvEohD.mjs} +0 -0
  185. /package/dist/{parse-args-BlRjqlkL.mjs → parse-args-B4cY5Vx5.mjs} +0 -0
  186. /package/dist/{ssrf-policy-ZaUfvhq7.mjs → ssrf-policy-Dsqd-DTX.mjs} +0 -0
  187. /package/dist/{types-CPhOReNX.mjs → types-DDBWs9UP.mjs} +0 -0
@@ -1,1531 +1,1756 @@
1
- import { i as argObject, o as argStringArray, s as argStringRequired } from "./parse-args-BlRjqlkL.mjs";
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";
2
4
  import { readFile as readFile$1, writeFile as writeFile$1 } from "node:fs/promises";
3
5
  import { isIP } from "node:net";
4
- //#region src/modules/protocol-analysis/ProtocolPatternUtils.ts
5
- const PRINTABLE_MIN = 32;
6
- const PRINTABLE_MAX = 126;
7
- const DELIMITER_CANDIDATES = [
8
- Buffer.from([44]),
9
- Buffer.from([124]),
10
- Buffer.from([58]),
11
- Buffer.from([59]),
12
- Buffer.from([9]),
13
- Buffer.from([0]),
14
- Buffer.from([13, 10])
15
- ];
16
- function normalizeHexPayload(value) {
17
- return value.replace(/^0x/i, "").replace(/\s+/g, "").toLowerCase();
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
+ };
18
192
  }
19
- function isHexPayload(value) {
20
- const normalized = normalizeHexPayload(value);
21
- if (normalized.length === 0 || normalized.length % 2 !== 0) return false;
22
- return /^[0-9a-f]+$/i.test(normalized);
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;
23
207
  }
24
- function parseHexPayload$1(value) {
25
- if (!isHexPayload(value)) return null;
26
- return Buffer.from(normalizeHexPayload(value), "hex");
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);
27
212
  }
28
- function isPrintableByte(value) {
29
- return value >= PRINTABLE_MIN && value <= PRINTABLE_MAX;
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
+ };
30
229
  }
31
- function printableRatio(buffer) {
32
- if (buffer.length === 0) return 0;
33
- let printableCount = 0;
34
- for (const value of buffer.values()) if (isPrintableByte(value)) printableCount += 1;
35
- return printableCount / buffer.length;
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
+ };
36
248
  }
37
- function averagePrintableRatio(buffers) {
38
- if (buffers.length === 0) return 0;
39
- return buffers.reduce((accumulator, buffer) => accumulator + printableRatio(buffer), 0) / buffers.length;
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
+ };
40
261
  }
41
- function splitBuffer(buffer, delimiter) {
42
- if (delimiter.length === 0) return [buffer];
43
- const parts = [];
44
- let start = 0;
45
- let index = buffer.indexOf(delimiter, start);
46
- while (index >= 0) {
47
- parts.push(buffer.subarray(start, index));
48
- start = index + delimiter.length;
49
- index = buffer.indexOf(delimiter, start);
50
- }
51
- parts.push(buffer.subarray(start));
52
- return parts;
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
+ };
53
275
  }
54
- function bufferToDelimiterString(buffer) {
55
- return printableRatio(buffer) === 1 ? buffer.toString("utf8") : buffer.toString("hex");
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
+ };
56
292
  }
57
- function parsePayloads(hexPayloads) {
58
- const buffers = [];
59
- for (const hexPayload of hexPayloads) {
60
- const payload = parseHexPayload$1(hexPayload);
61
- if (payload) buffers.push(payload);
62
- }
63
- return buffers;
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;
64
322
  }
65
- function decodeInteger(buffer, byteOrder) {
66
- if (buffer.length === 0) return null;
67
- if (buffer.length === 1) return buffer.readUInt8(0);
68
- if (buffer.length === 2) return byteOrder === "le" ? buffer.readUInt16LE(0) : buffer.readUInt16BE(0);
69
- if (buffer.length === 4) return byteOrder === "le" ? buffer.readUInt32LE(0) : buffer.readUInt32BE(0);
70
- if (buffer.length === 8) {
71
- const value = byteOrder === "le" ? Number(buffer.readBigUInt64LE(0)) : Number(buffer.readBigUInt64BE(0));
72
- return Number.isFinite(value) ? value : null;
73
- }
74
- let value = 0;
75
- const bytes = byteOrder === "le" ? [...buffer.values()].toReversed() : [...buffer.values()];
76
- for (const byte of bytes) value = value * 256 + byte;
77
- return Number.isFinite(value) ? value : null;
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;
78
326
  }
79
- function decodeFloat(buffer, byteOrder) {
80
- if (buffer.length === 4) {
81
- const value = byteOrder === "le" ? buffer.readFloatLE(0) : buffer.readFloatBE(0);
82
- return Number.isFinite(value) ? value : null;
83
- }
84
- if (buffer.length === 8) {
85
- const value = byteOrder === "le" ? buffer.readDoubleLE(0) : buffer.readDoubleBE(0);
86
- return Number.isFinite(value) ? value : null;
87
- }
88
- return null;
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;
89
330
  }
90
- function countOccurrences(buffer, delimiter) {
91
- if (delimiter.length === 0) return 0;
92
- let count = 0;
93
- let start = 0;
94
- let index = buffer.indexOf(delimiter, start);
95
- while (index >= 0) {
96
- count += 1;
97
- start = index + delimiter.length;
98
- index = buffer.indexOf(delimiter, start);
99
- }
100
- return count;
331
+ function parseInteger(value, label) {
332
+ if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${label} must be an integer`);
333
+ return value;
101
334
  }
102
- function inferFieldType(samples) {
103
- if (samples.length === 0) return "bytes";
104
- if (samples.every((sample) => sample.length === 1) && samples.every((sample) => sample[0] === 0 || sample[0] === 1)) return "bool";
105
- if (averagePrintableRatio(samples) >= .7) return "string";
106
- if (samples.every((sample) => sample.length === 4) && looksLikeFloatSamples(samples)) return "float";
107
- if (samples.every((sample) => sample.length <= 4)) return "int";
108
- return "bytes";
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;
109
339
  }
110
- function looksLikeFloatSamples(samples) {
111
- const decoded = [];
112
- for (const sample of samples) {
113
- const value = decodeFloat(sample, "be");
114
- if (value === null) return false;
115
- decoded.push(value);
116
- }
117
- return decoded.some((value) => Math.abs(value) > .001 && Math.abs(value) < 1e6);
340
+ function parseOptionalLength(value, label) {
341
+ return value === void 0 ? void 0 : parsePositiveInteger(value, label);
118
342
  }
119
- function isPrintableColumn(buffers, offset) {
120
- let valueCount = 0;
121
- let printableCount = 0;
122
- for (const buffer of buffers) {
123
- const value = buffer[offset];
124
- if (value === void 0) continue;
125
- valueCount += 1;
126
- if (isPrintableByte(value)) printableCount += 1;
127
- }
128
- return valueCount > 0 && printableCount / valueCount >= .8;
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;
129
347
  }
130
- function isBooleanColumn(buffers, offset) {
131
- let valueCount = 0;
132
- for (const buffer of buffers) {
133
- const value = buffer[offset];
134
- if (value === void 0) continue;
135
- valueCount += 1;
136
- if (value !== 0 && value !== 1) return false;
137
- }
138
- return valueCount > 0;
348
+ function expectString(value, label) {
349
+ if (typeof value !== "string") throw new Error(`${label} must be a string`);
350
+ return value;
139
351
  }
140
- function buildDelimitedFields(buffers, delimiter) {
141
- if (delimiter.length === 0) return [];
142
- const tokenized = buffers.map((buffer) => splitBuffer(buffer, delimiter));
143
- const firstRow = tokenized[0];
144
- if (!firstRow || firstRow.length < 2) return [];
145
- const tokenCount = firstRow.length;
146
- if (!tokenized.every((parts) => parts.length === tokenCount)) return [];
147
- const fields = [];
148
- let currentOffset = 0;
149
- for (let index = 0; index < tokenCount; index += 1) {
150
- const template = firstRow[index];
151
- if (!template) continue;
152
- const samples = tokenized.map((parts) => parts[index]).filter((part) => Buffer.isBuffer(part));
153
- fields.push({
154
- name: `field_${index + 1}`,
155
- offset: currentOffset,
156
- length: template.length,
157
- type: inferFieldType(samples)
158
- });
159
- currentOffset += template.length + delimiter.length;
160
- }
161
- return fields;
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();
162
357
  }
163
- function buildFixedWidthFields(buffers) {
164
- const minLength = Math.min(...buffers.map((buffer) => buffer.length));
165
- const fields = [];
166
- let offset = 0;
167
- while (offset < minLength && fields.length < 24) {
168
- if (isPrintableColumn(buffers, offset)) {
169
- let end = offset + 1;
170
- while (end < minLength && isPrintableColumn(buffers, end)) end += 1;
171
- fields.push({
172
- name: `field_${fields.length + 1}`,
173
- offset,
174
- length: end - offset,
175
- type: "string"
176
- });
177
- offset = end;
178
- continue;
179
- }
180
- if (isBooleanColumn(buffers, offset)) {
181
- fields.push({
182
- name: `field_${fields.length + 1}`,
183
- offset,
184
- length: 1,
185
- type: "bool"
186
- });
187
- offset += 1;
188
- continue;
189
- }
190
- const remaining = minLength - offset;
191
- if (remaining >= 4) {
192
- if (looksLikeFloatSamples(buffers.map((buffer) => buffer.subarray(offset, offset + 4)))) {
193
- fields.push({
194
- name: `field_${fields.length + 1}`,
195
- offset,
196
- length: 4,
197
- type: "float"
198
- });
199
- offset += 4;
200
- continue;
201
- }
202
- }
203
- const segmentLength = remaining >= 4 ? 4 : Math.min(remaining, 2);
204
- const samples = buffers.map((buffer) => buffer.subarray(offset, offset + segmentLength));
205
- fields.push({
206
- name: `field_${fields.length + 1}`,
207
- offset,
208
- length: segmentLength,
209
- type: inferFieldType(samples)
210
- });
211
- offset += segmentLength;
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");
212
364
  }
213
- return fields;
214
365
  }
215
- function inferDelimiter(buffers) {
216
- for (const candidate of DELIMITER_CANDIDATES) {
217
- const counts = buffers.map((buffer) => countOccurrences(buffer, candidate));
218
- const firstCount = counts[0];
219
- if (typeof firstCount === "number" && firstCount >= 2 && counts.every((count) => count === firstCount)) return bufferToDelimiterString(candidate);
220
- }
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
+ };
221
376
  }
222
- function inferByteOrder(buffers) {
223
- const minLength = Math.min(...buffers.map((buffer) => buffer.length));
224
- if (minLength < 2) return "be";
225
- let leScore = 0;
226
- let beScore = 0;
227
- const limit = Math.min(minLength - 1, 8);
228
- for (let offset = 0; offset < limit; offset += 2) {
229
- let leSmallValues = 0;
230
- let beSmallValues = 0;
231
- for (const buffer of buffers) {
232
- const little = buffer.readUInt16LE(offset);
233
- const big = buffer.readUInt16BE(offset);
234
- if (little < 4096) leSmallValues += 1;
235
- if (big < 4096) beSmallValues += 1;
236
- }
237
- if (leSmallValues > beSmallValues) leScore += 1;
238
- else if (beSmallValues > leSmallValues) beScore += 1;
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;
239
404
  }
240
- return leScore > beScore ? "le" : "be";
241
405
  }
242
- function labelMagicFields(fields, buffers) {
243
- if (fields.length === 0 || buffers.length < 2) return fields;
244
- const minLen = Math.min(...buffers.map((b) => b.length));
245
- let commonPrefixLen = 0;
246
- for (let offset = 0; offset < minLen; offset += 1) {
247
- const byte = buffers[0][offset];
248
- if (buffers.every((b) => b[offset] === byte)) commonPrefixLen = offset + 1;
249
- else break;
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;
250
432
  }
251
- if (commonPrefixLen === 0) return fields;
252
- let magicLabelApplied = false;
253
- return fields.map((field) => {
254
- if (!magicLabelApplied && field.offset === 0 && commonPrefixLen >= 2) {
255
- magicLabelApplied = true;
256
- return {
257
- ...field,
258
- name: "magic"
259
- };
260
- }
261
- if (magicLabelApplied && field.type === "int" && field.length <= 2 && field.offset <= commonPrefixLen) return {
262
- ...field,
263
- name: "version"
264
- };
265
- return field;
266
- });
267
433
  }
268
- //#endregion
269
- //#region src/modules/protocol-analysis/ProtocolPatternEngine.ts
270
- var ProtocolPatternEngine = class {
271
- patterns = /* @__PURE__ */ new Map();
272
- legacyPatterns = /* @__PURE__ */ new Map();
273
- definePattern(name, specOrFields, options) {
274
- const legacyPattern = Array.isArray(specOrFields) ? this.createLegacyPattern(name, specOrFields, options) : this.createLegacyPatternFromSpec(name, specOrFields);
275
- const spec = this.createSpecFromLegacyPattern(legacyPattern);
276
- this.patterns.set(name, spec);
277
- this.legacyPatterns.set(name, legacyPattern);
278
- if (Array.isArray(specOrFields)) return legacyPattern;
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);
279
439
  }
280
- detectPattern(hexPayload) {
281
- const payload = parseHexPayload$1(hexPayload);
282
- if (!payload) return null;
283
- let bestMatch = null;
284
- for (const pattern of this.patterns.values()) {
285
- const totalChecks = pattern.fields.length + (pattern.fieldDelimiter ? 1 : 0);
286
- if (totalChecks === 0) continue;
287
- let matches = 0;
288
- if (pattern.fieldDelimiter && this.payloadContainsDelimiter(payload, pattern.fieldDelimiter)) matches += 1;
289
- for (const field of pattern.fields) if (this.matchesField(payload, field, pattern.byteOrder ?? "be")) matches += 1;
290
- const confidence = Number((matches / totalChecks).toFixed(2));
291
- if (confidence <= 0) continue;
292
- const candidate = {
293
- pattern,
294
- confidence,
295
- matches,
296
- total: totalChecks
297
- };
298
- if (!bestMatch || candidate.confidence > bestMatch.confidence || candidate.confidence === bestMatch.confidence && candidate.matches > bestMatch.matches) bestMatch = candidate;
299
- }
300
- return bestMatch;
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);
301
444
  }
302
- autoDetect(hexPayloads) {
303
- const buffers = parsePayloads(hexPayloads);
304
- if (buffers.length === 0) return null;
305
- const delimiter = inferDelimiter(buffers);
306
- const fields = this.inferFields(hexPayloads);
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`);
307
467
  return {
308
- name: "auto-detected-pattern",
309
- fieldDelimiter: delimiter,
310
- byteOrder: inferByteOrder(buffers),
311
- fields
312
- };
313
- }
314
- inferFields(hexPayloads) {
315
- const buffers = parsePayloads(hexPayloads);
316
- if (buffers.length === 0) return [];
317
- const delimiter = inferDelimiter(buffers);
318
- if (delimiter) {
319
- const fields = buildDelimitedFields(buffers, this.parseDelimiter(delimiter));
320
- if (fields.length > 0) return labelMagicFields(fields, buffers);
321
- }
322
- return labelMagicFields(buildFixedWidthFields(buffers), buffers);
323
- }
324
- autoDetectPattern(payloads, options) {
325
- const hexPayloads = payloads.map((payload) => payload.toString("hex"));
326
- const detected = this.autoDetect(hexPayloads);
327
- const name = options?.name ?? detected?.name ?? "auto_detected";
328
- if (!detected) {
329
- const emptyPattern = this.createLegacyPattern(name, []);
330
- this.patterns.set(name, this.createSpecFromLegacyPattern(emptyPattern));
331
- this.legacyPatterns.set(name, emptyPattern);
332
- return emptyPattern;
333
- }
334
- const namedPattern = {
335
- ...detected,
336
- name
468
+ name,
469
+ type: fieldType,
470
+ value: numericValue
337
471
  };
338
- this.definePattern(name, namedPattern);
339
- return this.getPattern(name) ?? this.createLegacyPatternFromSpec(name, namedPattern);
340
- }
341
- getPattern(name) {
342
- return this.legacyPatterns.get(name);
343
472
  }
344
- listPatterns() {
345
- return [...this.patterns.keys()];
346
- }
347
- exportProto(pattern) {
348
- const legacyPattern = this.isLegacyPattern(pattern) ? pattern : this.createLegacyPatternFromSpec(pattern.name, pattern);
349
- const lines = [
350
- `// Protocol: ${legacyPattern.name}`,
351
- `// Byte order: ${legacyPattern.byteOrder}`,
352
- ""
353
- ];
354
- if (legacyPattern.encryption) {
355
- lines.push(`// Encryption: ${legacyPattern.encryption.type}`);
356
- if (legacyPattern.encryption.notes) lines.push(`// Notes: ${legacyPattern.encryption.notes}`);
357
- lines.push("");
358
- }
359
- lines.push(`message ${this.toPascalCase(legacyPattern.name)} {`);
360
- for (let index = 0; index < legacyPattern.fields.length; index += 1) {
361
- const field = legacyPattern.fields[index];
362
- if (!field) continue;
363
- const comment = field.description ? ` // ${field.description}` : "";
364
- lines.push(` ${this.toProtoType(field.type)} ${field.name} = ${index + 1};${comment}`);
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;
365
506
  }
366
- lines.push("}");
367
- lines.push("");
368
- return lines.join("\n");
369
- }
370
- payloadContainsDelimiter(payload, delimiter) {
371
- const delimiterBuffer = this.parseDelimiter(delimiter);
372
- if (delimiterBuffer.length === 0) return false;
373
- return payload.includes(delimiterBuffer);
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);
374
509
  }
375
- matchesField(payload, field, byteOrder) {
376
- if (field.offset < 0 || field.length <= 0 || payload.length < field.offset + field.length) return false;
377
- const slice = payload.subarray(field.offset, field.offset + field.length);
378
- switch (field.type) {
379
- case "bytes": return slice.length === field.length;
380
- case "bool": return slice.length === 1 && (slice[0] === 0 || slice[0] === 1);
381
- case "string": return printableRatio(slice) >= .6;
382
- case "int": return decodeInteger(slice, byteOrder) !== null;
383
- case "float": return decodeFloat(slice, byteOrder) !== null;
384
- default: return false;
385
- }
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;
386
525
  }
387
- createLegacyPattern(name, fields, options) {
388
- return {
389
- name,
390
- fields: fields.map((field) => ({
391
- name: field.name,
392
- offset: field.offset,
393
- length: field.length,
394
- type: field.type,
395
- ...field.description ? { description: field.description } : {}
396
- })).toSorted((left, right) => left.offset - right.offset),
397
- byteOrder: options?.byteOrder ?? "big",
398
- ...options?.encryption ? { encryption: options.encryption } : {}
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`)
399
542
  };
400
- }
401
- createLegacyPatternFromSpec(name, spec) {
402
- return {
403
- name,
404
- fieldDelimiter: spec.fieldDelimiter,
405
- byteOrder: spec.byteOrder === "le" ? "little" : "big",
406
- fields: spec.fields.map((field) => ({
407
- name: field.name,
408
- offset: field.offset,
409
- length: field.length,
410
- type: this.toLegacyFieldType(field),
411
- ...field.description ? { description: field.description } : {}
412
- }))
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
+ })()
413
551
  };
414
- }
415
- createSpecFromLegacyPattern(pattern) {
416
- return {
417
- name: pattern.name,
418
- fieldDelimiter: pattern.fieldDelimiter,
419
- byteOrder: pattern.byteOrder === "little" ? "le" : "be",
420
- fields: pattern.fields.map((field) => ({
421
- name: field.name,
422
- offset: field.offset,
423
- length: field.length,
424
- type: this.toSpecFieldType(field.type),
425
- ...field.description ? { description: field.description } : {}
426
- }))
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`)
427
556
  };
428
- }
429
- isLegacyPattern(pattern) {
430
- return pattern.byteOrder === "big" || pattern.byteOrder === "little";
431
- }
432
- toLegacyFieldType(field) {
433
- if (field.type === "float") return "float";
434
- if (field.type === "string") return "string";
435
- if (field.type === "bytes") return "bytes";
436
- if (field.length === 1) return "uint8";
437
- if (field.length === 2) return "uint16";
438
- if (field.length === 4) return "uint32";
439
- return "int64";
440
- }
441
- toSpecFieldType(fieldType) {
442
- if (fieldType === "float") return "float";
443
- if (fieldType === "string") return "string";
444
- if (fieldType === "bytes") return "bytes";
445
- return "int";
446
- }
447
- parseDelimiter(delimiter) {
448
- if (isHexPayload(delimiter)) {
449
- const parsed = parseHexPayload$1(delimiter);
450
- if (parsed) return parsed;
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
+ };
451
576
  }
452
- return Buffer.from(delimiter, "utf8");
453
- }
454
- toProtoType(type) {
455
- return {
456
- uint8: "uint32",
457
- uint16: "uint32",
458
- uint32: "uint32",
459
- int64: "int64",
460
- float: "float",
461
- string: "string",
462
- bytes: "bytes"
463
- }[type];
464
- }
465
- toPascalCase(name) {
466
- return name.replace(/[^a-zA-Z0-9_]/g, "_").split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("") || "Message";
467
577
  }
468
- };
469
- //#endregion
470
- //#region src/modules/protocol-analysis/StateMachineInferrer.ts
471
- function isRecord$1(value) {
472
- return value !== null && typeof value === "object" && !Array.isArray(value);
473
- }
474
- function normalizeText(value) {
475
- return value.trim().toLowerCase();
476
578
  }
477
- function calculateEntropy(buffer) {
478
- if (buffer.length === 0) return 0;
479
- const frequency = /* @__PURE__ */ new Map();
480
- for (const byte of buffer) frequency.set(byte, (frequency.get(byte) ?? 0) + 1);
481
- let entropy = 0;
482
- for (const count of frequency.values()) {
483
- const p = count / buffer.length;
484
- if (p > 0) entropy -= p * Math.log2(p);
485
- }
486
- return entropy;
487
- }
488
- function printableRatioOf(value) {
489
- if (value.length === 0) return 0;
490
- let count = 0;
491
- for (let i = 0; i < value.length; i += 1) {
492
- const code = value.charCodeAt(i);
493
- if (code >= 32 && code <= 126) count += 1;
494
- }
495
- return count / value.length;
496
- }
497
- var StateMachineInferrer = class {
498
- infer(messages) {
499
- if (messages.length === 0) return {
500
- states: [],
501
- transitions: [],
502
- initial: "",
503
- initialState: "",
504
- finalStates: []
505
- };
506
- const statesBySignature = /* @__PURE__ */ new Map();
507
- const transitionsByKey = /* @__PURE__ */ new Map();
508
- let previousStateId = "";
509
- for (let index = 0; index < messages.length; index += 1) {
510
- const message = messages[index];
511
- if (!message) continue;
512
- const signature = this.buildSignature(message);
513
- let state = statesBySignature.get(signature);
514
- if (!state) {
515
- state = {
516
- id: `state_${statesBySignature.size + 1}`,
517
- name: this.buildStateName(message, statesBySignature.size + 1),
518
- type: this.inferStateType(message, index === messages.length - 1)
519
- };
520
- statesBySignature.set(signature, state);
521
- } else state.type = this.mergeStateTypes(state.type, this.inferStateType(message, index === messages.length - 1));
522
- if (previousStateId && message.timestamp !== void 0) {
523
- const firstMessage = messages[0];
524
- if (firstMessage && firstMessage.timestamp !== void 0) state.timeout = message.timestamp - firstMessage.timestamp;
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;
525
598
  }
526
- if (previousStateId) {
527
- const event = this.buildEventName(message);
528
- const condition = this.buildCondition(message.fields);
529
- const action = this.buildAction(message);
530
- const transitionKey = `${previousStateId}:${state.id}:${event}`;
531
- if (!transitionsByKey.has(transitionKey)) transitionsByKey.set(transitionKey, {
532
- from: previousStateId,
533
- to: state.id,
534
- event,
535
- confidence: this.computeTransitionConfidence(message),
536
- ...condition ? { condition } : {},
537
- ...action ? { action } : {}
538
- });
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`
539
624
  }
540
- previousStateId = state.id;
541
- }
542
- const firstMessage = messages[0];
543
- const initial = firstMessage ? statesBySignature.get(this.buildSignature(firstMessage))?.id ?? "" : "";
544
- return {
545
- states: [...statesBySignature.values()],
546
- transitions: [...transitionsByKey.values()],
547
- initial,
548
- initialState: initial,
549
- finalStates: this.collectTerminalStates([...statesBySignature.values()])
550
625
  };
551
- }
552
- visualize(machine) {
553
- if (machine.states.length === 0) return [
554
- "```mermaid",
555
- "stateDiagram-v2",
556
- " [*] --> empty",
557
- "```"
558
- ].join("\n");
559
- const lines = ["```mermaid", "stateDiagram-v2"];
560
- const initial = machine.initialState ?? machine.initial;
561
- if (initial) lines.push(` [*] --> ${initial}`);
562
- for (const state of machine.states) {
563
- const stateType = state.type ?? "normal";
564
- const label = stateType === "normal" ? state.name : `${state.name} (${stateType})`;
565
- lines.push(` state "${label}" as ${state.id}`);
566
- }
567
- for (const transition of machine.transitions) {
568
- const parts = [transition.event ?? transition.trigger ?? "transition"];
569
- if (typeof transition.confidence === "number") parts.push(`(${transition.confidence.toFixed(2)})`);
570
- if (transition.condition) parts.push(`[${transition.condition}]`);
571
- if (transition.action) parts.push(`/ ${transition.action}`);
572
- lines.push(` ${transition.from} --> ${transition.to} : ${parts.join(" ")}`);
573
- }
574
- const finalStateSet = new Set(machine.finalStates ?? []);
575
- for (const state of machine.states) {
576
- const stateType = state.type ?? "normal";
577
- if (stateType === "accept" || stateType === "reject" || finalStateSet.has(state.id)) lines.push(` ${state.id} --> [*]`);
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
+ };
578
650
  }
579
- lines.push("```");
580
- return lines.join("\n");
581
651
  }
582
- inferStateMachine(messages) {
583
- const normalizedMessages = messages.map((message) => ({
584
- direction: message.direction === "out" ? "req" : "res",
585
- timestamp: message.timestamp ?? 0,
586
- fields: {},
587
- raw: message.payload.length > 0 ? message.payload.toString("utf8") : message.payload.toString("hex"),
588
- _rawBuffer: message.payload
589
- }));
590
- return this.infer(normalizedMessages);
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;
591
683
  }
592
- generateMermaid(machine) {
593
- return this.visualize(machine);
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;
594
692
  }
595
- simplify(machine) {
596
- if (machine.states.length < 2) return {
597
- ...machine,
598
- initialState: machine.initialState ?? machine.initial,
599
- finalStates: machine.finalStates ?? this.collectTerminalStates(machine.states)
600
- };
601
- const stateToGroup = /* @__PURE__ */ new Map();
602
- const groupRepresentative = /* @__PURE__ */ new Map();
603
- for (const state of machine.states) {
604
- const prefix = this.getPayloadPrefix(state);
605
- if (!prefix) continue;
606
- const existingGroup = [...groupRepresentative.entries()].find(([key]) => key === prefix);
607
- if (existingGroup) stateToGroup.set(state.id, existingGroup[0]);
608
- else {
609
- groupRepresentative.set(prefix, state.id);
610
- stateToGroup.set(state.id, prefix);
611
- }
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)];
612
721
  }
613
- const mergeMap = /* @__PURE__ */ new Map();
614
- const groupIdToPrimary = /* @__PURE__ */ new Map();
615
- for (const [prefix, primaryId] of groupRepresentative) groupIdToPrimary.set(prefix, primaryId);
616
- for (const state of machine.states) {
617
- const prefix = this.getPayloadPrefix(state);
618
- if (!prefix) continue;
619
- const primary = groupIdToPrimary.get(prefix);
620
- if (primary && primary !== state.id) mergeMap.set(state.id, primary);
621
- }
622
- if (mergeMap.size === 0) return {
623
- ...machine,
624
- initialState: machine.initialState ?? machine.initial,
625
- finalStates: machine.finalStates ?? this.collectTerminalStates(machine.states)
626
- };
627
- const newStates = machine.states.filter((state) => !mergeMap.has(state.id));
628
- const newTransitions = machine.transitions.map((t) => ({
629
- ...t,
630
- from: mergeMap.get(t.from) ?? t.from,
631
- to: mergeMap.get(t.to) ?? t.to
632
- })).filter((t) => t.from !== t.to);
633
- const rawInitialState = machine.initialState ?? machine.initial ?? "";
634
- const initialState = mergeMap.get(rawInitialState) ?? rawInitialState;
635
- return {
636
- states: newStates,
637
- transitions: newTransitions,
638
- initial: initialState,
639
- initialState,
640
- finalStates: machine.finalStates.map((fs) => mergeMap.get(fs) ?? fs).filter((fs, index, arr) => arr.indexOf(fs) === index)
641
- };
642
- }
643
- getPayloadPrefix(state) {
644
- const payload = state.expectedPayload;
645
- if (!payload || payload.length < 8) return null;
646
- return payload.slice(0, 8).toLowerCase();
647
- }
648
- buildSignature(message) {
649
- const fieldKeys = Object.keys(message.fields).toSorted().join(",");
650
- const rawPrefix = normalizeText(message._rawBuffer ? message._rawBuffer.toString("hex") : message.raw).slice(0, 24);
651
- return `${message.direction}|${fieldKeys}|${rawPrefix}`;
652
- }
653
- buildStateName(message, position) {
654
- const directionName = message.direction === "req" ? "send" : "recv";
655
- const primaryField = this.findPrimaryFieldName(message.fields);
656
- const raw = message.raw;
657
- if (raw.length === 0) return `${directionName}_empty`;
658
- const buf = message._rawBuffer;
659
- const hexContent = Buffer.isBuffer(buf) ? buf.toString("hex") : raw;
660
- if (hexContent.startsWith("16") || hexContent.startsWith("15") || hexContent.startsWith("17")) return `${directionName}_tls_handshake`;
661
- const trimmed = raw.trimStart();
662
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) return `${directionName}_json_${primaryField || `step_${position}`}`;
663
- if (printableRatioOf(raw) >= .7) {
664
- const lower = normalizeText(raw);
665
- if (lower.includes("close") || lower.includes("fin") || lower.includes("bye")) return `${directionName}_close`;
666
- if (lower.startsWith("get ") || lower.startsWith("post ") || lower.startsWith("http")) return `${directionName}_text_http`;
667
- return `${directionName}_text_${primaryField || `step_${position}`}`;
668
- }
669
- if (Buffer.isBuffer(buf) && buf.length >= 32) {
670
- if (calculateEntropy(buf) > 6) return `${directionName}_encrypted`;
671
- }
672
- if (Buffer.isBuffer(buf) && buf.length <= 4) return `${directionName}_control`;
673
- return `${directionName}_${primaryField || `step_${position}`}`;
674
- }
675
- findPrimaryFieldName(fields) {
676
- const firstKey = Object.keys(fields).toSorted()[0];
677
- return firstKey ? firstKey : "";
678
- }
679
- inferStateType(message, isLastMessage) {
680
- const text = normalizeText(message.raw);
681
- const statusValue = this.findStatusValue(message.fields);
682
- if (this.containsRejectSignal(text) || this.containsRejectSignal(statusValue)) return "reject";
683
- if (this.containsAcceptSignal(text) || this.containsAcceptSignal(statusValue) || isLastMessage && message.direction === "res") return "accept";
684
- return "normal";
685
- }
686
- mergeStateTypes(current, next) {
687
- if (current === "reject" || next === "reject") return "reject";
688
- if (current === "accept" || next === "accept") return "accept";
689
- return "normal";
690
- }
691
- findStatusValue(fields) {
692
- for (const key of [
693
- "status",
694
- "result",
695
- "code",
696
- "reason",
697
- "message"
698
- ]) {
699
- const value = fields[key];
700
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
701
- }
702
- return "";
703
- }
704
- containsRejectSignal(value) {
705
- return [
706
- "error",
707
- "fail",
708
- "denied",
709
- "reject",
710
- "timeout",
711
- "invalid"
712
- ].some((token) => normalizeText(value).includes(token));
713
- }
714
- containsAcceptSignal(value) {
715
- return [
716
- "ok",
717
- "success",
718
- "accept",
719
- "ready",
720
- "done",
721
- "complete"
722
- ].some((token) => normalizeText(value).includes(token));
723
- }
724
- buildEventName(message) {
725
- const statusValue = this.findStatusValue(message.fields);
726
- if (statusValue) return `${message.direction}_${normalizeText(statusValue).replace(/[^a-z0-9]+/g, "_")}`;
727
- const primaryField = this.findPrimaryFieldName(message.fields);
728
- if (primaryField) return `${message.direction}_${primaryField}`;
729
- return `${message.direction}_message`;
730
- }
731
- buildCondition(fields) {
732
- const statusValue = this.findStatusValue(fields);
733
- if (statusValue) return `status=${statusValue}`;
734
- const keys = Object.keys(fields).toSorted().slice(0, 2);
735
- if (keys.length === 0) return;
736
- const fragments = [];
737
- for (const key of keys) {
738
- const value = fields[key];
739
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") fragments.push(`${key}=${value}`);
740
- }
741
- return fragments.length > 0 ? fragments.join(", ") : void 0;
742
- }
743
- buildAction(message) {
744
- const statusValue = this.findStatusValue(message.fields);
745
- if (this.containsRejectSignal(statusValue) || this.containsRejectSignal(message.raw)) return "reject";
746
- if (this.containsAcceptSignal(statusValue) || this.containsAcceptSignal(message.raw)) return "complete";
747
- const rawText = normalizeText(message.raw);
748
- if (rawText.includes("ack")) return "acknowledge";
749
- if (rawText.includes("retry")) return "retry";
750
- if (Object.keys(message.fields).length > 0 && isRecord$1(message.fields)) return message.direction === "req" ? "send" : "receive";
751
- }
752
- collectTerminalStates(states) {
753
- const terminalIds = states.filter((state) => {
754
- const stateType = state.type ?? "normal";
755
- return stateType === "accept" || stateType === "reject";
756
- }).map((state) => state.id);
757
- for (const state of states) {
758
- const lower = normalizeText(state.name);
759
- if (lower.includes("close") || lower.includes("fin") || lower.includes("bye")) {
760
- if (!terminalIds.includes(state.id)) terminalIds.push(state.id);
761
- }
762
- }
763
- return terminalIds;
764
- }
765
- computeTransitionConfidence(message) {
766
- let confidence = .3;
767
- if (Object.keys(message.fields).length > 0) confidence += .3;
768
- if (this.findStatusValue(message.fields)) confidence += .2;
769
- if (message.raw.length > 0) confidence += .2;
770
- return Math.min(confidence, 1);
771
- }
772
- };
773
- //#endregion
774
- //#region src/server/domains/protocol-analysis/handlers/shared.ts
775
- function isRecord(value) {
776
- return value !== null && typeof value === "object" && !Array.isArray(value);
722
+ if (!/^[0-9a-f]{1,4}$/i.test(part)) throw new Error(`${label} contains an invalid IPv6 group`);
723
+ return [part];
724
+ });
777
725
  }
778
- function parseFieldSpec(value, index) {
779
- if (!isRecord(value)) throw new Error(`fields[${index}] must be an object`);
780
- const name = value.name;
781
- const offset = value.offset;
782
- const length = value.length;
783
- const type = value.type;
784
- if (typeof name !== "string" || name.trim().length === 0) throw new Error(`fields[${index}].name must be a non-empty string`);
785
- if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) throw new Error(`fields[${index}].offset must be a non-negative integer`);
786
- if (typeof length !== "number" || !Number.isInteger(length) || length <= 0) throw new Error(`fields[${index}].length must be a positive integer`);
787
- if (type !== "int" && type !== "string" && type !== "bytes" && type !== "bool" && type !== "float") throw new Error(`fields[${index}].type is invalid`);
788
- return {
789
- name,
790
- offset,
791
- length,
792
- type
793
- };
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;
794
743
  }
795
- function parseLegacyField(value, index) {
796
- if (!isRecord(value)) throw new Error(`fields[${index}] must be an object`);
797
- const name = value.name;
798
- const offset = value.offset;
799
- const length = value.length;
800
- const type = value.type;
801
- const description = value.description;
802
- if (typeof name !== "string" || name.trim().length === 0) throw new Error(`fields[${index}].name must be a non-empty string`);
803
- if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) throw new Error(`fields[${index}].offset must be a non-negative integer`);
804
- if (typeof length !== "number" || !Number.isInteger(length) || length <= 0) throw new Error(`fields[${index}].length must be a positive integer`);
805
- if (type !== "uint8" && type !== "uint16" && type !== "uint32" && type !== "int64" && type !== "float" && type !== "string" && type !== "bytes") throw new Error(`fields[${index}].type is invalid`);
806
- return {
807
- name,
808
- offset,
809
- length,
810
- type,
811
- ...typeof description === "string" ? { description } : {}
812
- };
744
+ function parseIpAddress(value, version, label) {
745
+ return version === "ipv4" ? parseIpv4Address(value, label) : parseIpv6Address(value, label);
813
746
  }
814
- function parsePatternSpec(name, value) {
815
- const rawFields = value.fields;
816
- if (!Array.isArray(rawFields)) throw new Error("spec.fields must be an array");
817
- const fieldDelimiter = typeof value.fieldDelimiter === "string" && value.fieldDelimiter.length > 0 ? value.fieldDelimiter : void 0;
818
- const byteOrderValue = value.byteOrder;
819
- const byteOrder = byteOrderValue === "le" || byteOrderValue === "be" ? byteOrderValue : void 0;
820
- return {
821
- name,
822
- ...fieldDelimiter ? { fieldDelimiter } : {},
823
- ...byteOrder ? { byteOrder } : {},
824
- fields: rawFields.map((field, index) => parseFieldSpec(field, index))
825
- };
747
+ function parseEtherType(value, label) {
748
+ return parseNamedOrNumericValue(value, label, ETHER_TYPE_MAP, 65535);
826
749
  }
827
- function parseEncryptionInfo(value) {
828
- if (!isRecord(value)) return;
829
- const type = value.type;
830
- if (type !== "aes" && type !== "xor" && type !== "rc4" && type !== "custom") return;
831
- const key = typeof value.key === "string" ? value.key : void 0;
832
- const iv = typeof value.iv === "string" ? value.iv : void 0;
833
- const notes = typeof value.notes === "string" ? value.notes : void 0;
834
- return {
835
- type,
836
- ...key ? { key } : {},
837
- ...iv ? { iv } : {},
838
- ...notes ? { notes } : {}
839
- };
750
+ function parseIpProtocol(value, label) {
751
+ return parseNamedOrNumericValue(value, label, IP_PROTOCOL_MAP, 255);
840
752
  }
841
- function parseProtocolMessage(value, index) {
842
- if (!isRecord(value)) throw new Error(`messages[${index}] must be an object`);
843
- const direction = value.direction;
844
- const timestamp = value.timestamp;
845
- const fields = value.fields;
846
- const raw = value.raw;
847
- if (direction !== "req" && direction !== "res") throw new Error(`messages[${index}].direction must be "req" or "res"`);
848
- if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) throw new Error(`messages[${index}].timestamp must be a number`);
849
- if (!isRecord(fields)) throw new Error(`messages[${index}].fields must be an object`);
850
- if (typeof raw !== "string") throw new Error(`messages[${index}].raw must be a string`);
851
- return {
852
- direction,
853
- timestamp,
854
- fields,
855
- raw
856
- };
753
+ function parsePcapLinkType(value, label) {
754
+ return parseNamedOrNumericValue(value, label, PCAP_LINK_TYPE_MAP, 4294967295);
857
755
  }
858
- const TEXT_ENCODINGS = ["utf8", "ascii"];
859
- const BINARY_ENCODINGS = [
860
- "utf8",
861
- "ascii",
862
- "hex",
863
- "base64"
864
- ];
865
- const PAYLOAD_FIELD_TYPES = [
866
- "u8",
867
- "u16",
868
- "u32",
869
- "i8",
870
- "i16",
871
- "i32",
872
- "string",
873
- "bytes"
874
- ];
875
- const MUTATION_STRATEGIES = [
876
- "set_byte",
877
- "flip_bit",
878
- "overwrite_bytes",
879
- "append_bytes",
880
- "truncate",
881
- "increment_integer"
882
- ];
883
- const ETHER_TYPE_MAP = Object.freeze({
884
- arp: 2054,
885
- ipv4: 2048,
886
- ipv6: 34525,
887
- vlan: 33024
888
- });
889
- const IP_PROTOCOL_MAP = Object.freeze({
890
- icmp: 1,
891
- igmp: 2,
892
- tcp: 6,
893
- udp: 17,
894
- gre: 47,
895
- esp: 50,
896
- ah: 51,
897
- icmpv6: 58,
898
- ospf: 89
899
- });
900
- const PCAP_LINK_TYPE_MAP = Object.freeze({
901
- loopback: 0,
902
- ethernet: 1,
903
- raw: 101
904
- });
905
- function parseEndian(value, fallback = "big") {
906
- return value === "little" ? "little" : fallback;
756
+ function parseChecksumEndian(value) {
757
+ return value === "little" ? "little" : "big";
907
758
  }
908
- function parseNonNegativeInteger(value, label) {
909
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw new Error(`${label} must be a non-negative integer`);
910
- return value;
759
+ function parsePacketEndianness(value) {
760
+ return value === "big" ? "big" : "little";
911
761
  }
912
- function parsePositiveInteger(value, label) {
913
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
914
- return value;
762
+ function parseTimestampPrecision(value) {
763
+ return value === "nano" ? "nano" : "micro";
915
764
  }
916
- function parseInteger(value, label) {
917
- if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${label} must be an integer`);
918
- return value;
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");
919
768
  }
920
- function parseByte(value, label) {
921
- const parsed = parseInteger(value, label);
922
- if (parsed < 0 || parsed > 255) throw new Error(`${label} must be between 0 and 255`);
923
- return parsed;
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;
924
780
  }
925
- function parseOptionalLength(value, label) {
926
- return value === void 0 ? void 0 : parsePositiveInteger(value, label);
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]);
927
787
  }
928
- function parseEncoding(value, allowed, fallback, label) {
929
- if (value === void 0) return fallback;
930
- if (typeof value !== "string" || !allowed.includes(value)) throw new Error(`${label} is invalid`);
931
- return value;
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;
932
809
  }
933
- function normalizeHexString(value, label) {
934
- const normalized = value.replace(/^0x/i, "").replace(/\s+/g, "");
935
- if (normalized.length === 0) return normalized;
936
- if (normalized.length % 2 !== 0 || /[^0-9a-f]/i.test(normalized)) throw new Error(`${label} must be a valid even-length hex string`);
937
- return normalized.toLowerCase();
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
+ };
938
829
  }
939
- function decodeBinaryValue(value, encoding, label) {
940
- switch (encoding) {
941
- case "utf8":
942
- case "ascii": return Buffer.from(value, encoding);
943
- case "hex": return Buffer.from(normalizeHexString(value, label), "hex");
944
- case "base64": return Buffer.from(value, "base64");
945
- }
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]);
946
840
  }
947
- function getNumericRange(width, signed) {
948
- const bits = width * 8;
949
- if (signed) return {
950
- min: -(2 ** (bits - 1)),
951
- max: 2 ** (bits - 1) - 1
952
- };
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);
953
851
  return {
954
- min: 0,
955
- max: 2 ** bits - 1
852
+ packet,
853
+ checksum
956
854
  };
957
855
  }
958
- function getFieldNumericMetadata(type) {
959
- switch (type) {
960
- case "u8": return {
961
- width: 1,
962
- signed: false
963
- };
964
- case "u16": return {
965
- width: 2,
966
- signed: false
967
- };
968
- case "u32": return {
969
- width: 4,
970
- signed: false
971
- };
972
- case "i8": return {
973
- width: 1,
974
- signed: true
975
- };
976
- case "i16": return {
977
- width: 2,
978
- signed: true
979
- };
980
- case "i32": return {
981
- width: 4,
982
- signed: true
983
- };
984
- default: return null;
985
- }
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);
986
861
  }
987
- function writeIntegerToBuffer(buffer, value, width, signed, endian) {
988
- if (signed) switch (width) {
989
- case 1:
990
- buffer.writeInt8(value, 0);
991
- return;
992
- case 2:
993
- if (endian === "little") buffer.writeInt16LE(value, 0);
994
- else buffer.writeInt16BE(value, 0);
995
- return;
996
- case 4:
997
- if (endian === "little") buffer.writeInt32LE(value, 0);
998
- else buffer.writeInt32BE(value, 0);
999
- return;
1000
- }
1001
- switch (width) {
1002
- case 1:
1003
- buffer.writeUInt8(value, 0);
1004
- return;
1005
- case 2:
1006
- if (endian === "little") buffer.writeUInt16LE(value, 0);
1007
- else buffer.writeUInt16BE(value, 0);
1008
- return;
1009
- case 4:
1010
- if (endian === "little") buffer.writeUInt32LE(value, 0);
1011
- else buffer.writeUInt32BE(value, 0);
1012
- return;
1013
- }
862
+ function writeUint16(buffer, offset, value, endianness) {
863
+ if (endianness === "little") buffer.writeUInt16LE(value, offset);
864
+ else buffer.writeUInt16BE(value, offset);
1014
865
  }
1015
- function readIntegerFromBuffer(buffer, offset, width, signed, endian) {
1016
- if (signed) switch (width) {
1017
- case 1: return buffer.readInt8(offset);
1018
- case 2: return endian === "little" ? buffer.readInt16LE(offset) : buffer.readInt16BE(offset);
1019
- case 4: return endian === "little" ? buffer.readInt32LE(offset) : buffer.readInt32BE(offset);
1020
- }
1021
- switch (width) {
1022
- case 1: return buffer.readUInt8(offset);
1023
- case 2: return endian === "little" ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
1024
- case 4: return endian === "little" ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
1025
- }
866
+ function readUint32(buffer, offset, endianness) {
867
+ return endianness === "little" ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
1026
868
  }
1027
- function applyFixedLength(encoded, length, padByte) {
1028
- if (length === void 0 || encoded.length === length) return encoded;
1029
- if (encoded.length > length) return encoded.subarray(0, length);
1030
- return Buffer.concat([encoded, Buffer.alloc(length - encoded.length, padByte)]);
869
+ function readUint16(buffer, offset, endianness) {
870
+ return endianness === "little" ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
1031
871
  }
1032
- function parsePayloadTemplateField(value, index) {
1033
- if (!isRecord(value)) throw new Error(`fields[${index}] must be an object`);
1034
- const name = value.name;
1035
- const type = value.type;
1036
- const rawValue = value.value;
1037
- if (typeof name !== "string" || name.trim().length === 0) throw new Error(`fields[${index}].name must be a non-empty string`);
1038
- if (typeof type !== "string" || !PAYLOAD_FIELD_TYPES.includes(type)) throw new Error(`fields[${index}].type is invalid`);
1039
- const fieldType = type;
1040
- const numericMetadata = getFieldNumericMetadata(fieldType);
1041
- if (numericMetadata) {
1042
- const numericValue = parseInteger(rawValue, `fields[${index}].value`);
1043
- const range = getNumericRange(numericMetadata.width, numericMetadata.signed);
1044
- if (numericValue < range.min || numericValue > range.max) throw new Error(`fields[${index}].value is out of range for ${type} (${range.min}..${range.max})`);
1045
- 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`);
1046
- return {
1047
- name,
1048
- type: fieldType,
1049
- value: numericValue
1050
- };
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");
1051
899
  }
1052
- if (typeof rawValue !== "string") throw new Error(`fields[${index}].value must be a string`);
1053
- const length = parseOptionalLength(value.length, `fields[${index}].length`);
1054
- const padByte = value.padByte === void 0 ? 0 : parseByte(value.padByte, `fields[${index}].padByte`);
1055
- if (type === "string") return {
1056
- name,
1057
- type: "string",
1058
- value: rawValue,
1059
- encoding: parseEncoding(value.encoding, TEXT_ENCODINGS, "utf8", `fields[${index}].encoding`),
1060
- ...length !== void 0 ? { length } : {},
1061
- padByte
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)
1062
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`);
1063
916
  return {
1064
- name,
1065
- type: "bytes",
1066
- value: rawValue,
1067
- encoding: parseEncoding(value.encoding, BINARY_ENCODINGS, "hex", `fields[${index}].encoding`),
1068
- ...length !== void 0 ? { length } : {},
1069
- padByte
917
+ data,
918
+ timestampSeconds,
919
+ timestampFraction,
920
+ originalLength
1070
921
  };
1071
922
  }
1072
- function encodePayloadTemplateField(field, endian) {
1073
- switch (field.type) {
1074
- case "u8":
1075
- case "u16":
1076
- case "u32":
1077
- case "i8":
1078
- case "i16":
1079
- case "i32": {
1080
- const numericMetadata = getFieldNumericMetadata(field.type);
1081
- if (!numericMetadata) throw new Error(`Unsupported numeric field type: ${field.type}`);
1082
- const buffer = Buffer.alloc(numericMetadata.width);
1083
- writeIntegerToBuffer(buffer, field.value, numericMetadata.width, numericMetadata.signed, endian);
1084
- return buffer;
1085
- }
1086
- case "string": return applyFixedLength(Buffer.from(field.value, field.encoding), field.length, field.padByte);
1087
- case "bytes": return applyFixedLength(decodeBinaryValue(field.value, field.encoding, `field ${field.name}`), field.length, field.padByte);
1088
- }
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]);
1089
941
  }
1090
- function buildPayloadFromTemplate(fields, endian) {
1091
- const buffers = [];
1092
- const segments = [];
1093
- let offset = 0;
1094
- for (const field of fields) {
1095
- const encoded = encodePayloadTemplateField(field, endian);
1096
- buffers.push(encoded);
1097
- segments.push({
1098
- name: field.name,
1099
- offset,
1100
- length: encoded.length,
1101
- hex: encoded.toString("hex")
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
1102
967
  });
1103
- offset += encoded.length;
1104
968
  }
1105
969
  return {
1106
- payload: Buffer.concat(buffers),
1107
- segments
970
+ header,
971
+ packets
1108
972
  };
1109
973
  }
1110
- function parsePayloadMutation(value, index) {
1111
- if (!isRecord(value)) throw new Error(`mutations[${index}] must be an object`);
1112
- const strategy = value.strategy;
1113
- if (typeof strategy !== "string" || !MUTATION_STRATEGIES.includes(strategy)) throw new Error(`mutations[${index}].strategy is invalid`);
1114
- switch (strategy) {
1115
- case "set_byte": return {
1116
- strategy: "set_byte",
1117
- offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
1118
- value: parseByte(value.value, `mutations[${index}].value`)
1119
- };
1120
- case "flip_bit": return {
1121
- strategy: "flip_bit",
1122
- offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
1123
- bit: (() => {
1124
- const bit = parseInteger(value.bit, `mutations[${index}].bit`);
1125
- if (bit < 0 || bit > 7) throw new Error(`mutations[${index}].bit must be between 0 and 7`);
1126
- return bit;
1127
- })()
1128
- };
1129
- case "overwrite_bytes": return {
1130
- strategy: "overwrite_bytes",
1131
- offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
1132
- data: decodeBinaryValue(typeof value.data === "string" ? value.data : (() => {
1133
- throw new Error(`mutations[${index}].data must be a string`);
1134
- })(), parseEncoding(value.encoding, BINARY_ENCODINGS, "hex", `mutations[${index}].encoding`), `mutations[${index}].data`)
1135
- };
1136
- case "append_bytes": return {
1137
- strategy: "append_bytes",
1138
- data: decodeBinaryValue(typeof value.data === "string" ? value.data : (() => {
1139
- throw new Error(`mutations[${index}].data must be a string`);
1140
- })(), parseEncoding(value.encoding, BINARY_ENCODINGS, "hex", `mutations[${index}].encoding`), `mutations[${index}].data`)
1141
- };
1142
- case "truncate": return {
1143
- strategy: "truncate",
1144
- length: parseNonNegativeInteger(value.length, `mutations[${index}].length`)
1145
- };
1146
- case "increment_integer": {
1147
- const width = value.width;
1148
- if (width !== 1 && width !== 2 && width !== 4) throw new Error(`mutations[${index}].width must be 1, 2, or 4`);
1149
- return {
1150
- strategy: "increment_integer",
1151
- offset: parseNonNegativeInteger(value.offset, `mutations[${index}].offset`),
1152
- width,
1153
- delta: parseInteger(value.delta, `mutations[${index}].delta`),
1154
- endian: parseEndian(value.endian),
1155
- signed: value.signed === true
1156
- };
1157
- }
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);
1158
1021
  }
1022
+ parts.push(buffer.subarray(start));
1023
+ return parts;
1159
1024
  }
1160
- function applyPayloadMutation(payload, mutation, index) {
1161
- const working = Buffer.from(payload);
1162
- switch (mutation.strategy) {
1163
- case "set_byte":
1164
- if (mutation.offset >= working.length) throw new Error(`mutations[${index}] offset is outside the payload`);
1165
- working[mutation.offset] = mutation.value;
1166
- return {
1167
- payload: working,
1168
- summary: {
1169
- index,
1170
- strategy: mutation.strategy,
1171
- detail: `set payload[${mutation.offset}] to ${mutation.value}`
1172
- }
1173
- };
1174
- case "flip_bit":
1175
- if (mutation.offset >= working.length) throw new Error(`mutations[${index}] offset is outside the payload`);
1176
- {
1177
- const currentByte = working.at(mutation.offset);
1178
- if (currentByte === void 0) throw new Error(`mutations[${index}] offset is outside the payload`);
1179
- working[mutation.offset] = currentByte ^ 1 << mutation.bit;
1180
- }
1181
- return {
1182
- payload: working,
1183
- summary: {
1184
- index,
1185
- strategy: mutation.strategy,
1186
- detail: `flipped bit ${mutation.bit} at offset ${mutation.offset}`
1187
- }
1188
- };
1189
- case "overwrite_bytes":
1190
- if (mutation.offset + mutation.data.length > working.length) throw new Error(`mutations[${index}] overwrite exceeds payload length`);
1191
- mutation.data.copy(working, mutation.offset);
1192
- return {
1193
- payload: working,
1194
- summary: {
1195
- index,
1196
- strategy: mutation.strategy,
1197
- detail: `overwrote ${mutation.data.length} bytes at offset ${mutation.offset}`
1198
- }
1199
- };
1200
- case "append_bytes": return {
1201
- payload: Buffer.concat([working, mutation.data]),
1202
- summary: {
1203
- index,
1204
- strategy: mutation.strategy,
1205
- detail: `appended ${mutation.data.length} bytes`
1206
- }
1207
- };
1208
- case "truncate":
1209
- if (mutation.length > working.length) throw new Error(`mutations[${index}] length exceeds payload size`);
1210
- return {
1211
- payload: working.subarray(0, mutation.length),
1212
- summary: {
1213
- index,
1214
- strategy: mutation.strategy,
1215
- detail: `truncated payload to ${mutation.length} bytes`
1216
- }
1217
- };
1218
- case "increment_integer": {
1219
- if (mutation.offset + mutation.width > working.length) throw new Error(`mutations[${index}] integer range exceeds payload length`);
1220
- const next = readIntegerFromBuffer(working, mutation.offset, mutation.width, mutation.signed, mutation.endian) + mutation.delta;
1221
- const range = getNumericRange(mutation.width, mutation.signed);
1222
- if (next < range.min || next > range.max) throw new Error(`mutations[${index}] integer overflow (${range.min}..${range.max})`);
1223
- writeIntegerToBuffer(working.subarray(mutation.offset, mutation.offset + mutation.width), next, mutation.width, mutation.signed, mutation.endian);
1224
- return {
1225
- payload: working,
1226
- summary: {
1227
- index,
1228
- strategy: mutation.strategy,
1229
- detail: `incremented ${mutation.signed ? "signed" : "unsigned"} ${mutation.width}-byte integer at offset ${mutation.offset} by ${mutation.delta}`
1230
- }
1231
- };
1232
- }
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);
1233
1033
  }
1034
+ return buffers;
1234
1035
  }
1235
- function parseMacAddress(value, label) {
1236
- if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${label} must be a non-empty MAC address string`);
1237
- const normalized = value.trim().toLowerCase().replace(/^0x/, "").replace(/[:\-.\s]/g, "");
1238
- if (!/^[0-9a-f]{12}$/i.test(normalized)) throw new Error(`${label} must be a valid 6-byte MAC address`);
1239
- const canonical = normalized.match(/.{2}/g)?.join(":");
1240
- if (!canonical) throw new Error(`${label} must be a valid 6-byte MAC address`);
1241
- return {
1242
- canonical,
1243
- bytes: Buffer.from(normalized, "hex")
1244
- };
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;
1245
1049
  }
1246
- function parseNamedOrNumericValue(value, label, map, max) {
1247
- if (typeof value === "number") {
1248
- if (!Number.isInteger(value) || value < 0 || value > max) throw new Error(`${label} must be an integer between 0 and ${max}`);
1249
- return value;
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;
1250
1054
  }
1251
- if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string or integer`);
1252
- const normalized = value.trim().toLowerCase();
1253
- const mapped = map[normalized];
1254
- if (mapped !== void 0) return mapped;
1255
- if (/^\d+$/.test(normalized)) {
1256
- const parsed = Number.parseInt(normalized, 10);
1257
- if (parsed > max) throw new Error(`${label} must be less than or equal to ${max}`);
1258
- return parsed;
1055
+ if (buffer.length === 8) {
1056
+ const value = byteOrder === "le" ? buffer.readDoubleLE(0) : buffer.readDoubleBE(0);
1057
+ return Number.isFinite(value) ? value : null;
1259
1058
  }
1260
- const hex = normalizeHexString(normalized, label);
1261
- const parsed = Number.parseInt(hex, 16);
1262
- if (parsed > max) throw new Error(`${label} must be less than or equal to ${max}`);
1263
- return parsed;
1264
- }
1265
- function parseIpv4Address(value, label) {
1266
- if (typeof value !== "string" || isIP(value.trim()) !== 4) throw new Error(`${label} must be a valid IPv4 address`);
1267
- const octets = value.trim().split(".").map((part) => Number.parseInt(part, 10));
1268
- return Buffer.from(octets);
1269
- }
1270
- function parseIpv6Groups(value, label) {
1271
- if (value.length === 0) return [];
1272
- return value.split(":").flatMap((part) => {
1273
- if (part.length === 0) return [];
1274
- if (part.includes(".")) {
1275
- const ipv4 = parseIpv4Address(part, label);
1276
- return [ipv4.readUInt16BE(0).toString(16), ipv4.readUInt16BE(2).toString(16)];
1277
- }
1278
- if (!/^[0-9a-f]{1,4}$/i.test(part)) throw new Error(`${label} contains an invalid IPv6 group`);
1279
- return [part];
1280
- });
1281
- }
1282
- function parseIpv6Address(value, label) {
1283
- if (typeof value !== "string") throw new Error(`${label} must be a valid IPv6 address`);
1284
- const normalized = value.trim().toLowerCase().split("%")[0] ?? "";
1285
- if (isIP(normalized) !== 6) throw new Error(`${label} must be a valid IPv6 address`);
1286
- const segments = normalized.split("::");
1287
- if (segments.length > 2) throw new Error(`${label} must be a valid IPv6 address`);
1288
- const head = parseIpv6Groups(segments[0] ?? "", label);
1289
- const tail = parseIpv6Groups(segments[1] ?? "", label);
1290
- const groups = segments.length === 2 ? [
1291
- ...head,
1292
- ...Array.from({ length: 8 - head.length - tail.length }, () => "0"),
1293
- ...tail
1294
- ] : head;
1295
- if (groups.length !== 8) throw new Error(`${label} must expand to exactly 8 IPv6 groups`);
1296
- const output = Buffer.alloc(16);
1297
- for (const [index, group] of groups.entries()) output.writeUInt16BE(Number.parseInt(group, 16), index * 2);
1298
- return output;
1299
- }
1300
- function parseIpAddress(value, version, label) {
1301
- return version === "ipv4" ? parseIpv4Address(value, label) : parseIpv6Address(value, label);
1302
- }
1303
- function parseEtherType(value, label) {
1304
- return parseNamedOrNumericValue(value, label, ETHER_TYPE_MAP, 65535);
1305
- }
1306
- function parseIpProtocol(value, label) {
1307
- return parseNamedOrNumericValue(value, label, IP_PROTOCOL_MAP, 255);
1308
- }
1309
- function parsePcapLinkType(value, label) {
1310
- return parseNamedOrNumericValue(value, label, PCAP_LINK_TYPE_MAP, 4294967295);
1059
+ return null;
1311
1060
  }
1312
- function parseChecksumEndian(value) {
1313
- return value === "little" ? "little" : "big";
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;
1314
1072
  }
1315
- function parsePacketEndianness(value) {
1316
- return value === "big" ? "big" : "little";
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";
1317
1080
  }
1318
- function parseTimestampPrecision(value) {
1319
- return value === "nano" ? "nano" : "micro";
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);
1320
1089
  }
1321
- function parseHexPayload(value, label) {
1322
- if (typeof value !== "string") throw new Error(`${label} must be a hex string`);
1323
- return Buffer.from(normalizeHexString(value, label), "hex");
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;
1324
1100
  }
1325
- function computeInternetChecksum(buffer) {
1326
- let sum = 0;
1327
- for (let offset = 0; offset < buffer.length; offset += 2) {
1328
- const high = buffer[offset] ?? 0;
1329
- const low = buffer[offset + 1] ?? 0;
1330
- sum += high << 8 | low;
1331
- while (sum > 65535) sum = (sum & 65535) + (sum >>> 16);
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;
1332
1108
  }
1333
- return ~sum & 65535;
1109
+ return valueCount > 0;
1334
1110
  }
1335
- function buildEthernetFrame(destinationMac, sourceMac, etherType, payload) {
1336
- const header = Buffer.alloc(14);
1337
- destinationMac.bytes.copy(header, 0);
1338
- sourceMac.bytes.copy(header, 6);
1339
- header.writeUInt16BE(etherType, 12);
1340
- return Buffer.concat([header, payload]);
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;
1341
1133
  }
1342
- function buildArpPayload(args) {
1343
- if (args.hardwareSize !== args.senderMac.bytes.length || args.hardwareSize !== args.targetMac.bytes.length) throw new Error("hardwareSize must match the provided MAC address lengths");
1344
- if (args.protocolSize !== args.senderIp.length || args.protocolSize !== args.targetIp.length) throw new Error("protocolSize must match the provided IP address lengths");
1345
- const buffer = Buffer.alloc(8 + args.hardwareSize * 2 + args.protocolSize * 2);
1134
+ function buildFixedWidthFields(buffers) {
1135
+ const minLength = Math.min(...buffers.map((buffer) => buffer.length));
1136
+ const fields = [];
1346
1137
  let offset = 0;
1347
- buffer.writeUInt16BE(args.hardwareType, offset);
1348
- offset += 2;
1349
- buffer.writeUInt16BE(args.protocolType, offset);
1350
- offset += 2;
1351
- buffer.writeUInt8(args.hardwareSize, offset++);
1352
- buffer.writeUInt8(args.protocolSize, offset++);
1353
- buffer.writeUInt16BE(args.operation === "reply" ? 2 : 1, offset);
1354
- offset += 2;
1355
- args.senderMac.bytes.copy(buffer, offset);
1356
- offset += args.hardwareSize;
1357
- args.senderIp.copy(buffer, offset);
1358
- offset += args.protocolSize;
1359
- args.targetMac.bytes.copy(buffer, offset);
1360
- offset += args.hardwareSize;
1361
- args.targetIp.copy(buffer, offset);
1362
- return buffer;
1363
- }
1364
- function buildIpv4Packet(args) {
1365
- const header = Buffer.alloc(20);
1366
- header[0] = 69;
1367
- header[1] = (args.dscp & 63) << 2 | args.ecn & 3;
1368
- header.writeUInt16BE(header.length + args.payload.length, 2);
1369
- header.writeUInt16BE(args.identification, 4);
1370
- const flags = (args.dontFragment ? 1 : 0) << 1 | (args.moreFragments ? 1 : 0);
1371
- header.writeUInt16BE((flags & 7) << 13 | args.fragmentOffset & 8191, 6);
1372
- header[8] = args.ttl;
1373
- header[9] = args.protocol;
1374
- header.writeUInt16BE(0, 10);
1375
- args.sourceIp.copy(header, 12);
1376
- args.destinationIp.copy(header, 16);
1377
- const checksum = computeInternetChecksum(header);
1378
- header.writeUInt16BE(checksum, 10);
1379
- return {
1380
- packet: Buffer.concat([header, args.payload]),
1381
- checksum
1382
- };
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;
1383
1185
  }
1384
- function buildIpv6Packet(args) {
1385
- const header = Buffer.alloc(40);
1386
- const versionTrafficFlow = 6 << 28 | (((args.dscp & 63) << 2 | args.ecn & 3) & 255) << 20 | args.flowLabel & 1048575;
1387
- header.writeUInt32BE(versionTrafficFlow >>> 0, 0);
1388
- header.writeUInt16BE(args.payload.length, 4);
1389
- header.writeUInt8(args.protocol, 6);
1390
- header.writeUInt8(args.hopLimit, 7);
1391
- args.sourceIp.copy(header, 8);
1392
- args.destinationIp.copy(header, 24);
1393
- return Buffer.concat([header, args.payload]);
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
+ }
1394
1192
  }
1395
- function buildIcmpEcho(args) {
1396
- const packet = Buffer.alloc(8 + args.payload.length);
1397
- packet[0] = args.operation === "reply" ? 0 : 8;
1398
- packet[1] = 0;
1399
- packet.writeUInt16BE(0, 2);
1400
- packet.writeUInt16BE(args.identifier, 4);
1401
- packet.writeUInt16BE(args.sequenceNumber, 6);
1402
- args.payload.copy(packet, 8);
1403
- const checksum = computeInternetChecksum(packet);
1404
- packet.writeUInt16BE(checksum, 2);
1405
- return {
1406
- packet,
1407
- checksum
1408
- };
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";
1409
1212
  }
1410
- function writeUint32(buffer, offset, value, endianness) {
1411
- if (endianness === "little") buffer.writeUInt32LE(value, offset);
1412
- else buffer.writeUInt32BE(value, offset);
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
+ });
1413
1238
  }
1414
- function writeUint16(buffer, offset, value, endianness) {
1415
- if (endianness === "little") buffer.writeUInt16LE(value, offset);
1416
- else buffer.writeUInt16BE(value, offset);
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);
1417
1444
  }
1418
- function readUint32(buffer, offset, endianness) {
1419
- return endianness === "little" ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
1445
+ function normalizeText(value) {
1446
+ return value.trim().toLowerCase();
1420
1447
  }
1421
- function readUint16(buffer, offset, endianness) {
1422
- return endianness === "little" ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
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;
1423
1458
  }
1424
- function getPcapMagic(endianness, precision) {
1425
- const hex = endianness === "little" ? precision === "nano" ? "4d3cb2a1" : "d4c3b2a1" : precision === "nano" ? "a1b23c4d" : "a1b2c3d4";
1426
- return Buffer.from(hex, "hex");
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;
1427
1467
  }
1428
- function parsePcapHeader(buffer) {
1429
- if (buffer.length < 24) throw new Error("PCAP file is too small to contain a global header");
1430
- const magic = buffer.subarray(0, 4).toString("hex");
1431
- let endianness;
1432
- let timestampPrecision;
1433
- switch (magic) {
1434
- case "d4c3b2a1":
1435
- endianness = "little";
1436
- timestampPrecision = "micro";
1437
- break;
1438
- case "4d3cb2a1":
1439
- endianness = "little";
1440
- timestampPrecision = "nano";
1441
- break;
1442
- case "a1b2c3d4":
1443
- endianness = "big";
1444
- timestampPrecision = "micro";
1445
- break;
1446
- case "a1b23c4d":
1447
- endianness = "big";
1448
- timestampPrecision = "nano";
1449
- break;
1450
- default: throw new Error("Unsupported capture format: only classic PCAP files are supported");
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
+ };
1451
1613
  }
1452
- return {
1453
- endianness,
1454
- timestampPrecision,
1455
- versionMajor: readUint16(buffer, 4, endianness),
1456
- versionMinor: readUint16(buffer, 6, endianness),
1457
- snapLength: readUint32(buffer, 16, endianness),
1458
- linkType: readUint32(buffer, 20, endianness)
1459
- };
1460
- }
1461
- function parsePcapPacketInput(value, index) {
1462
- if (!isRecord(value)) throw new Error(`packets[${index}] must be an object`);
1463
- const data = parseHexPayload(value.dataHex, `packets[${index}].dataHex`);
1464
- const timestampSeconds = value.timestampSeconds === void 0 ? 0 : parseNonNegativeInteger(value.timestampSeconds, `packets[${index}].timestampSeconds`);
1465
- const timestampFraction = value.timestampFraction === void 0 ? 0 : parseNonNegativeInteger(value.timestampFraction, `packets[${index}].timestampFraction`);
1466
- const originalLength = value.originalLength === void 0 ? data.length : parsePositiveInteger(value.originalLength, `packets[${index}].originalLength`);
1467
- if (originalLength < data.length) throw new Error(`packets[${index}].originalLength must be >= included packet length`);
1468
- return {
1469
- data,
1470
- timestampSeconds,
1471
- timestampFraction,
1472
- originalLength
1473
- };
1474
- }
1475
- function buildClassicPcap(args) {
1476
- const globalHeader = Buffer.alloc(24);
1477
- getPcapMagic(args.endianness, args.timestampPrecision).copy(globalHeader, 0);
1478
- writeUint16(globalHeader, 4, 2, args.endianness);
1479
- writeUint16(globalHeader, 6, 4, args.endianness);
1480
- writeUint32(globalHeader, 8, 0, args.endianness);
1481
- writeUint32(globalHeader, 12, 0, args.endianness);
1482
- writeUint32(globalHeader, 16, args.snapLength, args.endianness);
1483
- writeUint32(globalHeader, 20, args.linkType, args.endianness);
1484
- const records = args.packets.map((packet) => {
1485
- const header = Buffer.alloc(16);
1486
- writeUint32(header, 0, packet.timestampSeconds, args.endianness);
1487
- writeUint32(header, 4, packet.timestampFraction, args.endianness);
1488
- writeUint32(header, 8, packet.data.length, args.endianness);
1489
- writeUint32(header, 12, packet.originalLength, args.endianness);
1490
- return Buffer.concat([header, packet.data]);
1491
- });
1492
- return Buffer.concat([globalHeader, ...records]);
1493
- }
1494
- function readClassicPcap(buffer, maxPackets, maxBytesPerPacket) {
1495
- const header = parsePcapHeader(buffer);
1496
- const packets = [];
1497
- let offset = 24;
1498
- while (offset < buffer.length) {
1499
- if (maxPackets !== void 0 && packets.length >= maxPackets) break;
1500
- if (offset + 16 > buffer.length) throw new Error("PCAP file ends with an incomplete packet header");
1501
- const timestampSeconds = readUint32(buffer, offset, header.endianness);
1502
- const timestampFraction = readUint32(buffer, offset + 4, header.endianness);
1503
- const includedLength = readUint32(buffer, offset + 8, header.endianness);
1504
- const originalLength = readUint32(buffer, offset + 12, header.endianness);
1505
- offset += 16;
1506
- if (offset + includedLength > buffer.length) throw new Error("PCAP file ends with an incomplete packet payload");
1507
- const packetBytes = buffer.subarray(offset, offset + includedLength);
1508
- offset += includedLength;
1509
- const limit = maxBytesPerPacket === void 0 ? packetBytes.length : maxBytesPerPacket;
1510
- const visibleLength = Math.min(limit, packetBytes.length);
1511
- packets.push({
1512
- index: packets.length,
1513
- timestampSeconds,
1514
- timestampFraction,
1515
- includedLength,
1516
- originalLength,
1517
- dataHex: packetBytes.subarray(0, visibleLength).toString("hex"),
1518
- truncated: visibleLength < packetBytes.length
1519
- });
1614
+ getPayloadPrefix(state) {
1615
+ const payload = state.expectedPayload;
1616
+ if (!payload || payload.length < 8) return null;
1617
+ return payload.slice(0, 8).toLowerCase();
1520
1618
  }
1521
- return {
1522
- header,
1523
- packets
1524
- };
1525
- }
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
+ };
1526
1744
  //#endregion
1527
- //#region src/server/domains/protocol-analysis/handlers/handler-class.ts
1528
- var ProtocolAnalysisHandlers = class {
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 {
1529
1754
  engine;
1530
1755
  inferrer;
1531
1756
  eventBus;
@@ -1534,6 +1759,27 @@ var ProtocolAnalysisHandlers = class {
1534
1759
  this.inferrer = inferrer;
1535
1760
  this.eventBus = eventBus;
1536
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 {
1537
1783
  async handleDefinePattern(args) {
1538
1784
  try {
1539
1785
  const name = typeof args.name === "string" && args.name.trim().length > 0 ? args.name : "unnamed_pattern";
@@ -1571,7 +1817,7 @@ var ProtocolAnalysisHandlers = class {
1571
1817
  byteOrder: "big"
1572
1818
  },
1573
1819
  success: false,
1574
- error: error instanceof Error ? error.message : String(error)
1820
+ error: this.errorMessage(error)
1575
1821
  };
1576
1822
  }
1577
1823
  }
@@ -1598,10 +1844,9 @@ var ProtocolAnalysisHandlers = class {
1598
1844
  fields: [],
1599
1845
  byteOrder: "big"
1600
1846
  };
1601
- this.eventBus?.emit("protocol:pattern_detected", {
1847
+ this.emitEvent("protocol:pattern_detected", {
1602
1848
  patternName: namedPattern.name,
1603
- confidence: 0,
1604
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1849
+ confidence: 0
1605
1850
  });
1606
1851
  return {
1607
1852
  patterns: [result],
@@ -1611,7 +1856,7 @@ var ProtocolAnalysisHandlers = class {
1611
1856
  return {
1612
1857
  patterns: [],
1613
1858
  success: false,
1614
- error: error instanceof Error ? error.message : String(error)
1859
+ error: this.errorMessage(error)
1615
1860
  };
1616
1861
  }
1617
1862
  }
@@ -1626,7 +1871,7 @@ var ProtocolAnalysisHandlers = class {
1626
1871
  return {
1627
1872
  fields: [],
1628
1873
  success: false,
1629
- error: error instanceof Error ? error.message : String(error)
1874
+ error: this.errorMessage(error)
1630
1875
  };
1631
1876
  }
1632
1877
  }
@@ -1637,18 +1882,18 @@ var ProtocolAnalysisHandlers = class {
1637
1882
  if (!pattern) return { schema: `// Error: pattern '${patternId}' not found` };
1638
1883
  return { schema: this.getEngine().exportProto(pattern) };
1639
1884
  } catch (error) {
1640
- return { schema: `// Error: ${error instanceof Error ? error.message : String(error)}` };
1885
+ return { schema: `// Error: ${this.errorMessage(error)}` };
1641
1886
  }
1642
1887
  }
1643
1888
  async handleInferStateMachine(args) {
1644
1889
  try {
1645
1890
  const rawMessages = args.messages;
1646
1891
  if (!Array.isArray(rawMessages)) throw new Error("messages must be an array");
1647
- const hasLegacyShape = rawMessages.some((message) => isRecord(message) && (message.direction === "in" || message.direction === "out"));
1892
+ const hasLegacyShape = rawMessages.some((message) => isRecord$1(message) && (message.direction === "in" || message.direction === "out"));
1648
1893
  let stateMachine;
1649
1894
  if (hasLegacyShape) {
1650
1895
  const legacyMessages = rawMessages.map((message, index) => {
1651
- if (!isRecord(message)) throw new Error(`messages[${index}] must be an object`);
1896
+ if (!isRecord$1(message)) throw new Error(`messages[${index}] must be an object`);
1652
1897
  const direction = message.direction;
1653
1898
  const payloadHex = typeof message.payloadHex === "string" ? message.payloadHex : "";
1654
1899
  const timestamp = typeof message.timestamp === "number" ? message.timestamp : void 0;
@@ -1673,43 +1918,35 @@ var ProtocolAnalysisHandlers = class {
1673
1918
  };
1674
1919
  } catch (error) {
1675
1920
  return {
1676
- stateMachine: {
1677
- states: [],
1678
- transitions: [],
1679
- initial: "",
1680
- initialState: "",
1681
- finalStates: []
1682
- },
1921
+ stateMachine: { ...EMPTY_STATE_MACHINE },
1683
1922
  success: false,
1684
- error: error instanceof Error ? error.message : String(error)
1923
+ error: this.errorMessage(error)
1685
1924
  };
1686
1925
  }
1687
1926
  }
1688
1927
  async handleVisualizeState(args) {
1689
1928
  try {
1690
1929
  const stateMachineValue = args.stateMachine;
1691
- if (!isRecord(stateMachineValue)) return { mermaidDiagram: this.getInferrer().generateMermaid({
1692
- states: [],
1693
- transitions: [],
1694
- initial: "",
1695
- initialState: "",
1696
- finalStates: []
1697
- }) };
1930
+ if (!isRecord$1(stateMachineValue)) return { mermaidDiagram: this.getInferrer().generateMermaid(EMPTY_STATE_MACHINE) };
1698
1931
  const states = Array.isArray(stateMachineValue.states) ? stateMachineValue.states : [];
1699
1932
  const transitions = Array.isArray(stateMachineValue.transitions) ? stateMachineValue.transitions : [];
1700
1933
  const initialState = typeof stateMachineValue.initialState === "string" ? stateMachineValue.initialState : "";
1701
1934
  const finalStates = Array.isArray(stateMachineValue.finalStates) ? stateMachineValue.finalStates.filter((state) => typeof state === "string") : [];
1702
1935
  return { mermaidDiagram: this.getInferrer().generateMermaid({
1703
- states: states.filter((state) => isRecord(state)),
1704
- transitions: transitions.filter((transition) => isRecord(transition)),
1936
+ states: states.filter((state) => isRecord$1(state)),
1937
+ transitions: transitions.filter((transition) => isRecord$1(transition)),
1705
1938
  initial: initialState,
1706
1939
  initialState,
1707
1940
  finalStates
1708
1941
  }) };
1709
1942
  } catch (error) {
1710
- return { mermaidDiagram: `stateDiagram-v2\n note right of empty: ${error instanceof Error ? error.message : String(error)}` };
1943
+ return { mermaidDiagram: `stateDiagram-v2\n note right of empty: ${this.errorMessage(error)}` };
1711
1944
  }
1712
1945
  }
1946
+ };
1947
+ //#endregion
1948
+ //#region src/server/domains/protocol-analysis/handlers/payload-handlers.ts
1949
+ var ProtocolAnalysisPayloadHandlers = class extends ProtocolAnalysisPatternHandlers {
1713
1950
  async handlePayloadTemplateBuild(args) {
1714
1951
  try {
1715
1952
  const rawFields = args.fields;
@@ -1731,7 +1968,7 @@ var ProtocolAnalysisHandlers = class {
1731
1968
  byteLength: 0,
1732
1969
  fields: [],
1733
1970
  success: false,
1734
- error: error instanceof Error ? error.message : String(error)
1971
+ error: this.errorMessage(error)
1735
1972
  };
1736
1973
  }
1737
1974
  }
@@ -1768,16 +2005,20 @@ var ProtocolAnalysisHandlers = class {
1768
2005
  byteLength: 0,
1769
2006
  appliedMutations: [],
1770
2007
  success: false,
1771
- error: error instanceof Error ? error.message : String(error)
2008
+ error: this.errorMessage(error)
1772
2009
  };
1773
2010
  }
1774
2011
  }
2012
+ };
2013
+ //#endregion
2014
+ //#region src/server/domains/protocol-analysis/handlers/link-layer-handlers.ts
2015
+ var ProtocolAnalysisLinkLayerHandlers = class extends ProtocolAnalysisPayloadHandlers {
1775
2016
  async handleEthernetFrameBuild(args) {
1776
2017
  try {
1777
2018
  const destinationMac = parseMacAddress(args.destinationMac, "destinationMac");
1778
2019
  const sourceMac = parseMacAddress(args.sourceMac, "sourceMac");
1779
2020
  const etherType = parseEtherType(args.etherType, "etherType");
1780
- const frame = buildEthernetFrame(destinationMac, sourceMac, etherType, parseHexPayload(args.payloadHex, "payloadHex"));
2021
+ const frame = buildEthernetFrame(destinationMac, sourceMac, etherType, parseHexPayload$1(args.payloadHex, "payloadHex"));
1781
2022
  this.emitEvent("protocol:ethernet_frame_built", {
1782
2023
  byteLength: frame.length,
1783
2024
  etherType: `0x${etherType.toString(16).padStart(4, "0")}`
@@ -1802,7 +2043,7 @@ var ProtocolAnalysisHandlers = class {
1802
2043
  headerHex: "",
1803
2044
  frameHex: "",
1804
2045
  success: false,
1805
- error: error instanceof Error ? error.message : String(error)
2046
+ error: this.errorMessage(error)
1806
2047
  };
1807
2048
  }
1808
2049
  }
@@ -1848,14 +2089,18 @@ var ProtocolAnalysisHandlers = class {
1848
2089
  targetMac: "",
1849
2090
  targetIp: "",
1850
2091
  success: false,
1851
- error: error instanceof Error ? error.message : String(error)
2092
+ error: this.errorMessage(error)
1852
2093
  };
1853
2094
  }
1854
2095
  }
2096
+ };
2097
+ //#endregion
2098
+ //#region src/server/domains/protocol-analysis/handlers/ip-packet-handlers.ts
2099
+ var ProtocolAnalysisIpPacketHandlers = class extends ProtocolAnalysisLinkLayerHandlers {
1855
2100
  async handleRawIpPacketBuild(args) {
1856
2101
  try {
1857
2102
  const version = args.version === "ipv6" ? "ipv6" : "ipv4";
1858
- const payload = parseHexPayload(args.payloadHex ?? "", "payloadHex");
2103
+ const payload = parseHexPayload$1(args.payloadHex ?? "", "payloadHex");
1859
2104
  const protocol = parseIpProtocol(args.protocol, "protocol");
1860
2105
  const dscp = args.dscp === void 0 ? 0 : parseNonNegativeInteger(args.dscp, "dscp");
1861
2106
  const ecn = args.ecn === void 0 ? 0 : parseNonNegativeInteger(args.ecn, "ecn");
@@ -1937,7 +2182,7 @@ var ProtocolAnalysisHandlers = class {
1937
2182
  payloadHex: "",
1938
2183
  checksumHex: null,
1939
2184
  success: false,
1940
- error: error instanceof Error ? error.message : String(error)
2185
+ error: this.errorMessage(error)
1941
2186
  };
1942
2187
  }
1943
2188
  }
@@ -1948,7 +2193,7 @@ var ProtocolAnalysisHandlers = class {
1948
2193
  const sequenceNumber = args.sequenceNumber === void 0 ? 0 : parseNonNegativeInteger(args.sequenceNumber, "sequenceNumber");
1949
2194
  if (identifier > 65535) throw new Error("identifier must be between 0 and 65535");
1950
2195
  if (sequenceNumber > 65535) throw new Error("sequenceNumber must be between 0 and 65535");
1951
- const payload = parseHexPayload(args.payloadHex ?? "", "payloadHex");
2196
+ const payload = parseHexPayload$1(args.payloadHex ?? "", "payloadHex");
1952
2197
  const { packet, checksum } = buildIcmpEcho({
1953
2198
  operation,
1954
2199
  identifier,
@@ -1983,13 +2228,17 @@ var ProtocolAnalysisHandlers = class {
1983
2228
  packetHex: "",
1984
2229
  payloadHex: "",
1985
2230
  success: false,
1986
- error: error instanceof Error ? error.message : String(error)
2231
+ error: this.errorMessage(error)
1987
2232
  };
1988
2233
  }
1989
2234
  }
2235
+ };
2236
+ //#endregion
2237
+ //#region src/server/domains/protocol-analysis/handlers/checksum-handlers.ts
2238
+ var ProtocolAnalysisChecksumHandlers = class extends ProtocolAnalysisIpPacketHandlers {
1990
2239
  async handleChecksumApply(args) {
1991
2240
  try {
1992
- const payload = parseHexPayload(args.hexPayload, "hexPayload");
2241
+ const payload = parseHexPayload$1(args.hexPayload, "hexPayload");
1993
2242
  const rangeStart = args.startOffset === void 0 ? 0 : parseNonNegativeInteger(args.startOffset, "startOffset");
1994
2243
  const rangeEnd = args.endOffset === void 0 ? payload.length : parseNonNegativeInteger(args.endOffset, "endOffset");
1995
2244
  if (rangeStart > rangeEnd || rangeEnd > payload.length) throw new Error("checksum range must stay within the payload");
@@ -2031,13 +2280,23 @@ var ProtocolAnalysisHandlers = class {
2031
2280
  rangeStart: 0,
2032
2281
  rangeEnd: 0,
2033
2282
  success: false,
2034
- error: error instanceof Error ? error.message : String(error)
2283
+ error: this.errorMessage(error)
2035
2284
  };
2036
2285
  }
2037
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 {
2038
2297
  async handlePcapWrite(args) {
2039
2298
  try {
2040
- if (typeof args.path !== "string" || args.path.trim().length === 0) throw new Error("path must be a non-empty string");
2299
+ const path = this.parseRequiredPath(args);
2041
2300
  if (!Array.isArray(args.packets)) throw new Error("packets must be an array");
2042
2301
  const packets = args.packets.map((entry, index) => parsePcapPacketInput(entry, index));
2043
2302
  const endianness = parsePacketEndianness(args.endianness);
@@ -2051,14 +2310,14 @@ var ProtocolAnalysisHandlers = class {
2051
2310
  snapLength,
2052
2311
  linkType
2053
2312
  });
2054
- await writeFile$1(args.path, buffer);
2313
+ await writeFile$1(path, buffer);
2055
2314
  this.emitEvent("protocol:pcap_written", {
2056
- path: args.path,
2315
+ path,
2057
2316
  packetCount: packets.length,
2058
2317
  byteLength: buffer.length
2059
2318
  });
2060
2319
  return {
2061
- path: args.path,
2320
+ path,
2062
2321
  packetCount: packets.length,
2063
2322
  byteLength: buffer.length,
2064
2323
  endianness,
@@ -2075,22 +2334,22 @@ var ProtocolAnalysisHandlers = class {
2075
2334
  timestampPrecision: null,
2076
2335
  linkType: null,
2077
2336
  success: false,
2078
- error: error instanceof Error ? error.message : String(error)
2337
+ error: this.errorMessage(error)
2079
2338
  };
2080
2339
  }
2081
2340
  }
2082
2341
  async handlePcapRead(args) {
2083
2342
  try {
2084
- if (typeof args.path !== "string" || args.path.trim().length === 0) throw new Error("path must be a non-empty string");
2343
+ const path = this.parseRequiredPath(args);
2085
2344
  const maxPackets = args.maxPackets === void 0 ? void 0 : parsePositiveInteger(args.maxPackets, "maxPackets");
2086
2345
  const maxBytesPerPacket = args.maxBytesPerPacket === void 0 ? void 0 : parsePositiveInteger(args.maxBytesPerPacket, "maxBytesPerPacket");
2087
- const { header, packets } = readClassicPcap(await readFile$1(args.path), maxPackets, maxBytesPerPacket);
2346
+ const { header, packets } = readClassicPcap(await readFile$1(path), maxPackets, maxBytesPerPacket);
2088
2347
  this.emitEvent("protocol:pcap_read", {
2089
- path: args.path,
2348
+ path,
2090
2349
  packetCount: packets.length
2091
2350
  });
2092
2351
  return {
2093
- path: args.path,
2352
+ path,
2094
2353
  header,
2095
2354
  packets,
2096
2355
  success: true
@@ -2101,24 +2360,178 @@ var ProtocolAnalysisHandlers = class {
2101
2360
  header: null,
2102
2361
  packets: [],
2103
2362
  success: false,
2104
- error: error instanceof Error ? error.message : String(error)
2363
+ error: this.errorMessage(error)
2105
2364
  };
2106
2365
  }
2107
2366
  }
2108
- emitEvent(event, payload) {
2109
- this.eventBus?.emit(event, {
2110
- ...payload,
2111
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2112
- });
2113
- }
2114
- getEngine() {
2115
- if (!this.engine) this.engine = new ProtocolPatternEngine();
2116
- return this.engine;
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;
2117
2370
  }
2118
- getInferrer() {
2119
- if (!this.inferrer) this.inferrer = new StateMachineInferrer();
2120
- return this.inferrer;
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
+ });
2121
2528
  }
2122
2529
  };
2123
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
2124
2537
  export { ProtocolAnalysisHandlers };