oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

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 (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,746 @@
1
+ """Wireshark Lua dissector generator from ProtocolSpec.
2
+
3
+ This module generates functional Wireshark Lua dissectors from ProtocolSpec
4
+ objects (from reverse engineering workflows). The generated dissectors can
5
+ be loaded into Wireshark for interactive protocol analysis and validation.
6
+
7
+ Features:
8
+ - Generate working Lua dissectors from ProtocolSpec
9
+ - Support all field types (uint8, uint16, uint32, string, bytes, enum)
10
+ - CRC validation in Lua
11
+ - Test PCAP generation for validation
12
+ - Lua syntax validation
13
+
14
+ Example:
15
+ >>> from oscura.export.wireshark_dissector import (
16
+ ... WiresharkDissectorGenerator,
17
+ ... DissectorConfig
18
+ ... )
19
+ >>> from oscura.workflows.reverse_engineering import ProtocolSpec, FieldSpec
20
+ >>> spec = ProtocolSpec(
21
+ ... name="MyProtocol",
22
+ ... baud_rate=115200,
23
+ ... frame_format="8N1",
24
+ ... sync_pattern="aa55",
25
+ ... frame_length=10,
26
+ ... fields=[
27
+ ... FieldSpec(name="sync", offset=0, size=2, field_type="bytes"),
28
+ ... FieldSpec(name="length", offset=2, size=1, field_type="uint8"),
29
+ ... ],
30
+ ... checksum_type=None,
31
+ ... checksum_position=None,
32
+ ... confidence=0.95
33
+ ... )
34
+ >>> config = DissectorConfig(protocol_name="MyProtocol", port=5000)
35
+ >>> generator = WiresharkDissectorGenerator(config)
36
+ >>> dissector_path, pcap_path = generator.generate(
37
+ ... spec,
38
+ ... sample_messages=[b"\\xaa\\x55\\x08test123"],
39
+ ... output_path=Path("myproto.lua")
40
+ ... )
41
+
42
+ Installation:
43
+ Copy the generated .lua file to your Wireshark plugins directory:
44
+ - Linux: ~/.local/lib/wireshark/plugins/
45
+ - macOS: ~/.config/wireshark/plugins/
46
+ - Windows: %APPDATA%\\Wireshark\\plugins\\
47
+
48
+ References:
49
+ - Wireshark Lua API: https://wiki.wireshark.org/LuaAPI
50
+ - Lua Dissectors: https://wiki.wireshark.org/Lua/Dissectors
51
+ """
52
+
53
+ from __future__ import annotations
54
+
55
+ import logging
56
+ import struct
57
+ import subprocess
58
+ from dataclasses import dataclass
59
+ from datetime import UTC, datetime
60
+ from pathlib import Path
61
+ from typing import TYPE_CHECKING
62
+
63
+ from oscura.utils.validation import validate_protocol_spec
64
+
65
+ if TYPE_CHECKING:
66
+ from oscura.inference.crc_reverse import CRCParameters
67
+ from oscura.workflows.reverse_engineering import ProtocolSpec
68
+
69
+ __all__ = ["DissectorConfig", "WiresharkDissectorGenerator"]
70
+
71
+ logger = logging.getLogger(__name__)
72
+
73
+
74
+ @dataclass
75
+ class DissectorConfig:
76
+ """Configuration for Wireshark dissector generation.
77
+
78
+ Attributes:
79
+ protocol_name: Protocol name for dissector.
80
+ port: UDP/TCP port number for registration (None for no registration).
81
+ include_crc_validation: Include CRC validation code in dissector.
82
+ generate_test_pcap: Generate test PCAP file with sample messages.
83
+ wireshark_version: Target Wireshark version (default "3.0+").
84
+ """
85
+
86
+ protocol_name: str
87
+ port: int | None = None
88
+ include_crc_validation: bool = True
89
+ generate_test_pcap: bool = True
90
+ wireshark_version: str = "3.0+"
91
+
92
+
93
+ class WiresharkDissectorGenerator:
94
+ """Generate functional Wireshark Lua dissectors from ProtocolSpec.
95
+
96
+ This class converts ProtocolSpec objects (from reverse engineering workflows)
97
+ into Wireshark Lua dissectors that can be loaded into Wireshark for protocol
98
+ analysis and validation.
99
+
100
+ Features:
101
+ - All field types (uint8, uint16, uint32, string, bytes, enum)
102
+ - CRC validation in Lua
103
+ - Test PCAP generation
104
+ - Lua syntax validation
105
+ - UDP/TCP port registration
106
+
107
+ Example:
108
+ >>> config = DissectorConfig(protocol_name="MyProtocol", port=5000)
109
+ >>> generator = WiresharkDissectorGenerator(config)
110
+ >>> dissector_path, pcap_path = generator.generate(
111
+ ... spec,
112
+ ... sample_messages=[b"\\x01\\x02\\x03"],
113
+ ... output_path=Path("myproto.lua")
114
+ ... )
115
+ """
116
+
117
+ def __init__(self, config: DissectorConfig) -> None:
118
+ """Initialize dissector generator.
119
+
120
+ Args:
121
+ config: Dissector generation configuration.
122
+ """
123
+ self.config = config
124
+
125
+ def generate(
126
+ self,
127
+ spec: ProtocolSpec,
128
+ sample_messages: list[bytes],
129
+ output_path: Path,
130
+ ) -> tuple[Path, Path | None]:
131
+ """Generate Wireshark Lua dissector and optional test PCAP.
132
+
133
+ Args:
134
+ spec: Protocol specification from reverse engineering.
135
+ sample_messages: Sample protocol messages for test PCAP.
136
+ output_path: Path for output .lua file.
137
+
138
+ Returns:
139
+ Tuple of (dissector_lua_path, test_pcap_path).
140
+ test_pcap_path is None if generate_test_pcap is False.
141
+
142
+ Raises:
143
+ ValueError: If spec is invalid or has missing required fields.
144
+ RuntimeError: If Lua syntax validation fails.
145
+ OSError: If file writing fails.
146
+
147
+ Example:
148
+ >>> spec = ProtocolSpec(name="test", ...)
149
+ >>> generator = WiresharkDissectorGenerator(config)
150
+ >>> lua_path, pcap_path = generator.generate(
151
+ ... spec,
152
+ ... [b"\\x01\\x02\\x03"],
153
+ ... Path("test.lua")
154
+ ... )
155
+ """
156
+ # Validate spec
157
+ self._validate_spec(spec)
158
+
159
+ # Generate Lua code
160
+ lua_code = self._generate_lua_dissector(spec)
161
+
162
+ # Validate Lua syntax
163
+ if not self._validate_lua_syntax(lua_code):
164
+ raise RuntimeError("Generated Lua code has syntax errors")
165
+
166
+ # Write Lua file
167
+ output_path.parent.mkdir(parents=True, exist_ok=True)
168
+ output_path.write_text(lua_code, encoding="utf-8")
169
+ logger.info(f"Generated Lua dissector: {output_path}")
170
+
171
+ # Generate test PCAP if requested
172
+ pcap_path = None
173
+ if self.config.generate_test_pcap and sample_messages:
174
+ pcap_path = output_path.with_suffix(".pcap")
175
+ self._generate_test_pcap(sample_messages, pcap_path)
176
+ logger.info(f"Generated test PCAP: {pcap_path}")
177
+
178
+ return output_path, pcap_path
179
+
180
+ def _validate_spec(self, spec: ProtocolSpec) -> None:
181
+ """Validate protocol specification.
182
+
183
+ Args:
184
+ spec: Protocol specification to validate.
185
+
186
+ Raises:
187
+ ValueError: If spec is invalid.
188
+ """
189
+ validate_protocol_spec(spec)
190
+
191
+ # Validate fields
192
+ for field in spec.fields:
193
+ if not field.name:
194
+ raise ValueError("Field name is required")
195
+ if field.field_type not in {
196
+ "uint8",
197
+ "uint16",
198
+ "uint32",
199
+ "bytes",
200
+ "string",
201
+ "constant",
202
+ "checksum",
203
+ }:
204
+ raise ValueError(f"Unsupported field type: {field.field_type}")
205
+
206
+ def _generate_lua_dissector(self, spec: ProtocolSpec) -> str:
207
+ """Generate complete Lua dissector code.
208
+
209
+ Args:
210
+ spec: Protocol specification.
211
+
212
+ Returns:
213
+ Complete Lua dissector code as string.
214
+ """
215
+ sections = [
216
+ self._generate_header(spec),
217
+ self._generate_protocol_declaration(spec),
218
+ self._generate_field_declarations(spec),
219
+ self._generate_crc_validator(spec) if self.config.include_crc_validation else "",
220
+ self._generate_dissector_function(spec),
221
+ self._generate_registration(spec),
222
+ ]
223
+
224
+ return "\n\n".join(s for s in sections if s)
225
+
226
+ def _generate_header(self, spec: ProtocolSpec) -> str:
227
+ """Generate Lua file header with installation instructions.
228
+
229
+ Args:
230
+ spec: Protocol specification.
231
+
232
+ Returns:
233
+ Lua header comment block.
234
+ """
235
+ return f"""-- Wireshark Lua Dissector for {spec.name}
236
+ -- Generated by Oscura on {datetime.now(UTC).isoformat()}
237
+ --
238
+ -- Installation:
239
+ -- Copy this file to your Wireshark plugins directory:
240
+ -- - Linux: ~/.local/lib/wireshark/plugins/
241
+ -- - macOS: ~/.config/wireshark/plugins/
242
+ -- - Windows: %APPDATA%\\Wireshark\\plugins\\
243
+ --
244
+ -- Protocol: {spec.name}
245
+ -- Frame Format: {spec.frame_format}
246
+ -- Baud Rate: {spec.baud_rate} bps
247
+ -- Frame Length: {spec.frame_length if spec.frame_length else "Variable"} bytes
248
+ -- Sync Pattern: {spec.sync_pattern}
249
+ -- Checksum: {spec.checksum_type if spec.checksum_type else "None"}
250
+ -- Confidence: {spec.confidence:.2f}
251
+ --
252
+ -- Wireshark Version: {self.config.wireshark_version}
253
+ """
254
+
255
+ def _generate_protocol_declaration(self, spec: ProtocolSpec) -> str:
256
+ """Generate protocol declaration (Proto object).
257
+
258
+ Args:
259
+ spec: Protocol specification.
260
+
261
+ Returns:
262
+ Lua code for protocol declaration.
263
+ """
264
+ proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
265
+ return f"""-- Protocol declaration
266
+ local {proto_var}_proto = Proto("{proto_var}", "{spec.name}")"""
267
+
268
+ def _generate_field_declarations(self, spec: ProtocolSpec) -> str:
269
+ """Generate ProtoField declarations for all fields.
270
+
271
+ Args:
272
+ spec: Protocol specification.
273
+
274
+ Returns:
275
+ Lua code for field declarations.
276
+ """
277
+ proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
278
+ lines = ["-- Field declarations"]
279
+
280
+ for field in spec.fields:
281
+ field_var = f"f_{field.name}"
282
+ field_path = f"{proto_var}.{field.name}"
283
+ display_name = field.name.replace("_", " ").title()
284
+
285
+ # Determine ProtoField type and base
286
+ if field.field_type == "uint8":
287
+ proto_type = "uint8"
288
+ base = "base.HEX"
289
+ elif field.field_type == "uint16":
290
+ proto_type = "uint16"
291
+ base = "base.HEX"
292
+ elif field.field_type == "uint32":
293
+ proto_type = "uint32"
294
+ base = "base.HEX"
295
+ elif field.field_type == "string":
296
+ proto_type = "string"
297
+ base = "base.UNICODE"
298
+ elif field.field_type in ("bytes", "constant"):
299
+ proto_type = "bytes"
300
+ base = "base.SPACE"
301
+ else: # checksum
302
+ proto_type = "uint16" # Assume 16-bit checksum
303
+ base = "base.HEX"
304
+
305
+ # Handle enum values
306
+ if hasattr(field, "enum") and field.enum:
307
+ # Generate value_string table
308
+ enum_var = f"vs_{field.name}"
309
+ lines.append(f"local {enum_var} = {{")
310
+ for key, value in field.enum.items():
311
+ lines.append(f' [{key}] = "{value}",')
312
+ lines.append("}")
313
+ lines.append(
314
+ f"local {field_var} = ProtoField.{proto_type}("
315
+ f'"{field_path}", "{display_name}", {base}, {enum_var})'
316
+ )
317
+ else:
318
+ lines.append(
319
+ f"local {field_var} = ProtoField.{proto_type}("
320
+ f'"{field_path}", "{display_name}", {base})'
321
+ )
322
+
323
+ # Register fields with protocol
324
+ lines.append("")
325
+ lines.append(f"{proto_var}_proto.fields = {{")
326
+ for field in spec.fields:
327
+ lines.append(f" f_{field.name},")
328
+ lines.append("}")
329
+
330
+ return "\n".join(lines)
331
+
332
+ def _generate_crc_validator(self, spec: ProtocolSpec) -> str:
333
+ """Generate CRC validation function in Lua.
334
+
335
+ Args:
336
+ spec: Protocol specification.
337
+
338
+ Returns:
339
+ Lua CRC validation function code.
340
+ """
341
+ if not spec.checksum_type or spec.checksum_type not in ("crc8", "crc16", "crc32"):
342
+ return ""
343
+
344
+ # Get CRC parameters if available
345
+ crc_info = getattr(spec, "crc_info", None)
346
+ if crc_info:
347
+ return self._generate_crc_function_from_params(crc_info)
348
+
349
+ # Default CRC implementations for common types
350
+ if spec.checksum_type == "crc16":
351
+ return """-- CRC-16-CCITT validation
352
+ function validate_crc16(buffer, offset, length)
353
+ local crc = 0xFFFF
354
+ local poly = 0x1021
355
+
356
+ for i = 0, length - 1 do
357
+ local byte = buffer(offset + i, 1):uint()
358
+ crc = bit32.bxor(crc, bit32.lshift(byte, 8))
359
+
360
+ for bit = 0, 7 do
361
+ if bit32.band(crc, 0x8000) ~= 0 then
362
+ crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), 0xFFFF)
363
+ else
364
+ crc = bit32.band(bit32.lshift(crc, 1), 0xFFFF)
365
+ end
366
+ end
367
+ end
368
+
369
+ return crc
370
+ end"""
371
+ elif spec.checksum_type == "crc8":
372
+ return """-- CRC-8 validation
373
+ function validate_crc8(buffer, offset, length)
374
+ local crc = 0x00
375
+ local poly = 0x07
376
+
377
+ for i = 0, length - 1 do
378
+ local byte = buffer(offset + i, 1):uint()
379
+ crc = bit32.bxor(crc, byte)
380
+
381
+ for bit = 0, 7 do
382
+ if bit32.band(crc, 0x80) ~= 0 then
383
+ crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), 0xFF)
384
+ else
385
+ crc = bit32.band(bit32.lshift(crc, 1), 0xFF)
386
+ end
387
+ end
388
+ end
389
+
390
+ return crc
391
+ end"""
392
+ else: # crc32
393
+ return """-- CRC-32 validation
394
+ function validate_crc32(buffer, offset, length)
395
+ local crc = 0xFFFFFFFF
396
+ local poly = 0x04C11DB7
397
+
398
+ for i = 0, length - 1 do
399
+ local byte = buffer(offset + i, 1):uint()
400
+ crc = bit32.bxor(crc, bit32.lshift(byte, 24))
401
+
402
+ for bit = 0, 7 do
403
+ if bit32.band(crc, 0x80000000) ~= 0 then
404
+ crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), 0xFFFFFFFF)
405
+ else
406
+ crc = bit32.band(bit32.lshift(crc, 1), 0xFFFFFFFF)
407
+ end
408
+ end
409
+ end
410
+
411
+ return bit32.bxor(crc, 0xFFFFFFFF)
412
+ end"""
413
+
414
+ def _generate_crc_function_from_params(self, crc_info: CRCParameters) -> str:
415
+ """Generate CRC function from CRCParameters.
416
+
417
+ Args:
418
+ crc_info: CRC parameters from reverse engineering.
419
+
420
+ Returns:
421
+ Lua CRC function code.
422
+ """
423
+ width = crc_info.width
424
+ poly = crc_info.polynomial
425
+ init = crc_info.init
426
+ xor_out = crc_info.xor_out
427
+ mask = (1 << width) - 1
428
+
429
+ func_name = f"validate_crc{width}"
430
+
431
+ code = [
432
+ f"-- CRC-{width} validation (Custom parameters)",
433
+ f"-- Polynomial: 0x{poly:0{width // 4}x}",
434
+ f"-- Init: 0x{init:0{width // 4}x}",
435
+ f"-- XorOut: 0x{xor_out:0{width // 4}x}",
436
+ f"-- ReflectIn: {str(crc_info.reflect_in).lower()}",
437
+ f"-- ReflectOut: {str(crc_info.reflect_out).lower()}",
438
+ f"function {func_name}(buffer, offset, length)",
439
+ f" local crc = 0x{init:0{width // 4}x}",
440
+ f" local poly = 0x{poly:0{width // 4}x}",
441
+ f" local mask = 0x{mask:0{width // 4}x}",
442
+ " ",
443
+ " for i = 0, length - 1 do",
444
+ " local byte = buffer(offset + i, 1):uint()",
445
+ ]
446
+
447
+ if crc_info.reflect_in:
448
+ code.extend(
449
+ [
450
+ " -- Reflect input byte",
451
+ " local reflected = 0",
452
+ " for b = 0, 7 do",
453
+ " if bit32.band(byte, bit32.lshift(1, b)) ~= 0 then",
454
+ " reflected = bit32.bor(reflected, bit32.lshift(1, 7 - b))",
455
+ " end",
456
+ " end",
457
+ " byte = reflected",
458
+ ]
459
+ )
460
+
461
+ code.extend(
462
+ [
463
+ f" crc = bit32.bxor(crc, bit32.lshift(byte, {width - 8}))",
464
+ " ",
465
+ " for bit = 0, 7 do",
466
+ f" if bit32.band(crc, 0x{1 << (width - 1):0{width // 4}x}) ~= 0 then",
467
+ " crc = bit32.band(bit32.bxor(bit32.lshift(crc, 1), poly), mask)",
468
+ " else",
469
+ " crc = bit32.band(bit32.lshift(crc, 1), mask)",
470
+ " end",
471
+ " end",
472
+ " end",
473
+ " ",
474
+ ]
475
+ )
476
+
477
+ if crc_info.reflect_out:
478
+ code.extend(
479
+ [
480
+ " -- Reflect output CRC",
481
+ " local reflected = 0",
482
+ f" for b = 0, {width - 1} do",
483
+ " if bit32.band(crc, bit32.lshift(1, b)) ~= 0 then",
484
+ f" reflected = bit32.bor(reflected, bit32.lshift(1, {width - 1} - b))",
485
+ " end",
486
+ " end",
487
+ " crc = reflected",
488
+ ]
489
+ )
490
+
491
+ code.extend(
492
+ [
493
+ f" return bit32.bxor(crc, 0x{xor_out:0{width // 4}x})",
494
+ "end",
495
+ ]
496
+ )
497
+
498
+ return "\n".join(code)
499
+
500
+ def _generate_dissector_function(self, spec: ProtocolSpec) -> str:
501
+ """Generate main dissector function.
502
+
503
+ Args:
504
+ spec: Protocol specification.
505
+
506
+ Returns:
507
+ Lua dissector function code.
508
+ """
509
+ proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
510
+ min_length = spec.frame_length if spec.frame_length else 1
511
+
512
+ lines = [
513
+ "-- Main dissector function",
514
+ f"function {proto_var}_proto.dissector(buffer, pinfo, tree)",
515
+ " -- Check minimum length",
516
+ f" if buffer:len() < {min_length} then",
517
+ " return 0",
518
+ " end",
519
+ " ",
520
+ f' pinfo.cols.protocol = "{spec.name}"',
521
+ " ",
522
+ f' local subtree = tree:add({proto_var}_proto, buffer(), "{spec.name}")',
523
+ " local offset = 0",
524
+ " ",
525
+ ]
526
+
527
+ # Extract fields
528
+ for field in spec.fields:
529
+ field_size = field.size if isinstance(field.size, int) else 1
530
+
531
+ # Determine buffer reader
532
+ if field.field_type == "uint8":
533
+ reader = f"buffer(offset, {field_size}):uint()"
534
+ elif field.field_type == "uint16" or field.field_type == "uint32":
535
+ endian = getattr(field, "endian", "big")
536
+ if endian == "little":
537
+ reader = f"buffer(offset, {field_size}):le_uint()"
538
+ else:
539
+ reader = f"buffer(offset, {field_size}):uint()"
540
+ elif field.field_type == "string":
541
+ reader = f"buffer(offset, {field_size}):string()"
542
+ else: # bytes, constant, checksum
543
+ reader = f"buffer(offset, {field_size})"
544
+
545
+ lines.append(f" -- Field: {field.name}")
546
+ lines.append(f" subtree:add(f_{field.name}, {reader})")
547
+ lines.append(f" offset = offset + {field_size}")
548
+ lines.append(" ")
549
+
550
+ # Add CRC validation
551
+ if spec.checksum_type and spec.checksum_position is not None:
552
+ width_map = {"crc8": 8, "crc16": 16, "crc32": 32}
553
+ width = width_map.get(spec.checksum_type, 16)
554
+ crc_size = width // 8
555
+
556
+ if spec.checksum_position == -1:
557
+ # CRC is at the end
558
+ lines.extend(
559
+ [
560
+ " -- Validate CRC",
561
+ f" local data_length = buffer:len() - {crc_size}",
562
+ f" local computed_crc = validate_crc{width}(buffer, 0, data_length)",
563
+ f" local packet_crc = buffer(data_length, {crc_size}):uint()",
564
+ " if computed_crc == packet_crc then",
565
+ f' subtree:add(buffer(data_length, {crc_size}), "CRC: Valid")',
566
+ " else",
567
+ ' subtree:add_expert_info(PI_CHECKSUM, PI_ERROR, "CRC: Invalid")',
568
+ " end",
569
+ " ",
570
+ ]
571
+ )
572
+
573
+ lines.extend(
574
+ [
575
+ " return buffer:len()",
576
+ "end",
577
+ ]
578
+ )
579
+
580
+ return "\n".join(lines)
581
+
582
+ def _generate_registration(self, spec: ProtocolSpec) -> str:
583
+ """Generate protocol registration code.
584
+
585
+ Args:
586
+ spec: Protocol specification.
587
+
588
+ Returns:
589
+ Lua registration code.
590
+ """
591
+ proto_var = spec.name.lower().replace(" ", "_").replace("-", "_")
592
+
593
+ if self.config.port is None:
594
+ return f"""-- Protocol registration (manual)
595
+ -- To use this dissector:
596
+ -- 1. Open Wireshark
597
+ -- 2. Right-click on a packet
598
+ -- 3. Select "Decode As..."
599
+ -- 4. Choose "{spec.name}"
600
+ --
601
+ -- Or register on a specific port by adding:
602
+ -- DissectorTable.get("udp.port"):add(YOUR_PORT, {proto_var}_proto)"""
603
+
604
+ return f"""-- Protocol registration
605
+ -- Register on UDP port {self.config.port}
606
+ local udp_port = DissectorTable.get("udp.port")
607
+ udp_port:add({self.config.port}, {proto_var}_proto)
608
+
609
+ -- Also register on TCP port {self.config.port}
610
+ local tcp_port = DissectorTable.get("tcp.port")
611
+ tcp_port:add({self.config.port}, {proto_var}_proto)"""
612
+
613
+ def _validate_lua_syntax(self, lua_code: str) -> bool:
614
+ """Validate Lua syntax using luac if available.
615
+
616
+ Args:
617
+ lua_code: Lua code to validate.
618
+
619
+ Returns:
620
+ True if syntax is valid or luac not available, False if errors found.
621
+ """
622
+ try:
623
+ # Try to run luac syntax check
624
+ result = subprocess.run(
625
+ ["luac", "-p", "-"],
626
+ input=lua_code.encode("utf-8"),
627
+ capture_output=True,
628
+ timeout=5,
629
+ check=False,
630
+ )
631
+ if result.returncode != 0:
632
+ logger.error(f"Lua syntax error: {result.stderr.decode('utf-8')}")
633
+ return False
634
+ logger.info("Lua syntax validation passed")
635
+ return True
636
+ except FileNotFoundError:
637
+ # luac not available, skip validation
638
+ logger.warning("luac not found, skipping Lua syntax validation")
639
+ return True
640
+ except subprocess.TimeoutExpired:
641
+ logger.warning("Lua syntax validation timed out")
642
+ return True
643
+ except Exception as e:
644
+ logger.warning(f"Lua syntax validation failed: {e}")
645
+ return True
646
+
647
+ def _generate_test_pcap(self, sample_messages: list[bytes], output_path: Path) -> None:
648
+ """Generate test PCAP file with sample messages.
649
+
650
+ Creates a PCAP file with UDP packets containing the sample messages.
651
+ This allows testing the dissector in Wireshark.
652
+
653
+ Args:
654
+ sample_messages: List of protocol messages to include.
655
+ output_path: Path for output .pcap file.
656
+
657
+ Raises:
658
+ OSError: If file writing fails.
659
+ """
660
+ pcap_data = bytearray(self._build_pcap_header())
661
+ dst_port = self.config.port if self.config.port else 5000
662
+
663
+ for i, message in enumerate(sample_messages):
664
+ packet = self._build_udp_packet(i, message, dst_port)
665
+ pcap_data.extend(self._build_packet_header(packet))
666
+
667
+ output_path.write_bytes(pcap_data)
668
+
669
+ def _build_pcap_header(self) -> bytes:
670
+ """Build PCAP global header."""
671
+ return struct.pack(
672
+ "<IHHIIII",
673
+ 0xA1B2C3D4,
674
+ 2,
675
+ 4,
676
+ 0,
677
+ 0,
678
+ 65535,
679
+ 1, # Magic, versions, timezone, accuracy, snaplen, linktype
680
+ )
681
+
682
+ def _build_udp_packet(self, index: int, message: bytes, dst_port: int) -> bytes:
683
+ """Build complete UDP packet with Ethernet/IP/UDP headers."""
684
+ src_ip = bytes([192, 168, 1, 1])
685
+ dst_ip = bytes([192, 168, 1, 2])
686
+
687
+ eth_header = (
688
+ bytes([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) # Dst MAC
689
+ + bytes([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]) # Src MAC
690
+ + bytes([0x08, 0x00]) # IPv4
691
+ )
692
+
693
+ ip_header = self._build_ip_header(index, len(message), src_ip, dst_ip)
694
+ udp_header = struct.pack(">HHHH", 50000 + index, dst_port, 8 + len(message), 0)
695
+
696
+ return eth_header + ip_header + udp_header + message
697
+
698
+ def _build_ip_header(
699
+ self, packet_id: int, payload_len: int, src_ip: bytes, dst_ip: bytes
700
+ ) -> bytes:
701
+ """Build IP header with checksum calculation."""
702
+ ip_total_length = 20 + 8 + payload_len
703
+ header = struct.pack(
704
+ ">BBHHHBBH4s4s", 0x45, 0, ip_total_length, packet_id + 1, 0, 64, 17, 0, src_ip, dst_ip
705
+ )
706
+ checksum = self._calculate_ip_checksum(header)
707
+ return struct.pack(
708
+ ">BBHHHBBH4s4s",
709
+ 0x45,
710
+ 0,
711
+ ip_total_length,
712
+ packet_id + 1,
713
+ 0,
714
+ 64,
715
+ 17,
716
+ checksum,
717
+ src_ip,
718
+ dst_ip,
719
+ )
720
+
721
+ def _build_packet_header(self, packet: bytes) -> bytes:
722
+ """Build PCAP packet header."""
723
+ timestamp = int(datetime.now(UTC).timestamp())
724
+ return struct.pack("<IIII", timestamp, 0, len(packet), len(packet)) + packet
725
+
726
+ def _calculate_ip_checksum(self, header: bytes) -> int:
727
+ """Calculate IP header checksum.
728
+
729
+ Args:
730
+ header: IP header bytes.
731
+
732
+ Returns:
733
+ Checksum value.
734
+ """
735
+ # Sum all 16-bit words
736
+ checksum = 0
737
+ for i in range(0, len(header), 2):
738
+ word = (header[i] << 8) + (header[i + 1] if i + 1 < len(header) else 0)
739
+ checksum += word
740
+
741
+ # Add carry bits
742
+ while checksum >> 16:
743
+ checksum = (checksum & 0xFFFF) + (checksum >> 16)
744
+
745
+ # One's complement
746
+ return ~checksum & 0xFFFF