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,801 @@
1
+ """Scapy layer generator from ProtocolSpec.
2
+
3
+ This module generates production-ready Scapy layer classes from ProtocolSpec
4
+ objects (from reverse engineering workflows). The generated layers can be
5
+ imported and used for packet construction, dissection, and network analysis.
6
+
7
+ Features:
8
+ - Generate complete Scapy layer classes from ProtocolSpec
9
+ - Support all field types (uint8, uint16, uint32, string, bytes, enum)
10
+ - Handle endianness (big-endian, little-endian)
11
+ - CRC validation using Scapy's XByteField
12
+ - Generate importable Python modules
13
+ - Validate generated code executes without errors
14
+
15
+ Example:
16
+ >>> from oscura.export.scapy_layer import (
17
+ ... ScapyLayerGenerator,
18
+ ... ScapyLayerConfig
19
+ ... )
20
+ >>> from oscura.workflows.reverse_engineering import ProtocolSpec, FieldSpec
21
+ >>> spec = ProtocolSpec(
22
+ ... name="MyProtocol",
23
+ ... baud_rate=115200,
24
+ ... frame_format="8N1",
25
+ ... sync_pattern="aa55",
26
+ ... frame_length=10,
27
+ ... fields=[
28
+ ... FieldSpec(name="sync", offset=0, size=2, field_type="bytes"),
29
+ ... FieldSpec(name="length", offset=2, size=1, field_type="uint8"),
30
+ ... ],
31
+ ... checksum_type="crc16",
32
+ ... checksum_position=-1,
33
+ ... confidence=0.95
34
+ ... )
35
+ >>> config = ScapyLayerConfig(protocol_name="MyProtocol")
36
+ >>> generator = ScapyLayerGenerator(config)
37
+ >>> layer_path = generator.generate(
38
+ ... spec,
39
+ ... sample_messages=[b"\\xaa\\x55\\x08test123"],
40
+ ... output_path=Path("myproto_layer.py")
41
+ ... )
42
+ >>> # Import and use the generated layer
43
+ >>> from myproto_layer import MyProtocol
44
+ >>> pkt = MyProtocol(sync=b"\\xaa\\x55", length=8)
45
+
46
+ Installation:
47
+ Import the generated .py file directly or add it to your Python path:
48
+ >>> import sys
49
+ >>> sys.path.append('/path/to/generated')
50
+ >>> from myproto_layer import MyProtocol
51
+
52
+ References:
53
+ - Scapy Documentation: https://scapy.readthedocs.io/
54
+ - Scapy Layer Creation: https://scapy.readthedocs.io/en/latest/build_dissect.html
55
+ """
56
+
57
+ from __future__ import annotations
58
+
59
+ import ast
60
+ import logging
61
+ from dataclasses import dataclass
62
+ from datetime import UTC, datetime
63
+ from pathlib import Path
64
+ from typing import TYPE_CHECKING
65
+
66
+ from tqdm import tqdm
67
+
68
+ from oscura.utils.validation import validate_protocol_spec
69
+
70
+ if TYPE_CHECKING:
71
+ from oscura.inference.crc_reverse import CRCParameters
72
+ from oscura.workflows.reverse_engineering import FieldSpec, ProtocolSpec
73
+
74
+ __all__ = ["ScapyLayerConfig", "ScapyLayerGenerator"]
75
+
76
+ logger = logging.getLogger(__name__)
77
+
78
+
79
+ @dataclass
80
+ class ScapyLayerConfig:
81
+ """Configuration for Scapy layer generation.
82
+
83
+ Attributes:
84
+ protocol_name: Protocol name for layer class.
85
+ base_class: Scapy base class (default "Packet").
86
+ include_crc_validation: Include CRC validation code (default True).
87
+ generate_examples: Generate usage examples in docstring (default True).
88
+ show_progress: Show progress bar for >100 messages (default True).
89
+ """
90
+
91
+ protocol_name: str
92
+ base_class: str = "Packet"
93
+ include_crc_validation: bool = True
94
+ generate_examples: bool = True
95
+ show_progress: bool = True
96
+
97
+
98
+ class ScapyLayerGenerator:
99
+ """Generate production-ready Scapy layers from ProtocolSpec.
100
+
101
+ This class converts ProtocolSpec objects (from reverse engineering workflows)
102
+ into Scapy layer classes that can be imported and used for packet construction,
103
+ dissection, and network analysis.
104
+
105
+ Features:
106
+ - All field types (uint8, uint16, uint32, string, bytes, enum)
107
+ - Endianness handling (big/little)
108
+ - CRC validation
109
+ - Python code validation
110
+ - Usage examples
111
+
112
+ Example:
113
+ >>> config = ScapyLayerConfig(protocol_name="MyProtocol")
114
+ >>> generator = ScapyLayerGenerator(config)
115
+ >>> layer_path = generator.generate(
116
+ ... spec,
117
+ ... sample_messages=[b"\\x01\\x02\\x03"],
118
+ ... output_path=Path("myproto_layer.py")
119
+ ... )
120
+ >>> from myproto_layer import MyProtocol
121
+ >>> pkt = MyProtocol()
122
+ """
123
+
124
+ def __init__(self, config: ScapyLayerConfig) -> None:
125
+ """Initialize Scapy layer generator.
126
+
127
+ Args:
128
+ config: Layer generation configuration.
129
+ """
130
+ self.config = config
131
+
132
+ def generate(
133
+ self,
134
+ spec: ProtocolSpec,
135
+ sample_messages: list[bytes],
136
+ output_path: Path,
137
+ ) -> Path:
138
+ """Generate Scapy layer Python module.
139
+
140
+ Args:
141
+ spec: Protocol specification from reverse engineering.
142
+ sample_messages: Sample protocol messages for examples.
143
+ output_path: Path for output .py file.
144
+
145
+ Returns:
146
+ Path to generated Python module.
147
+
148
+ Raises:
149
+ ValueError: If spec is invalid or has missing required fields.
150
+ RuntimeError: If Python syntax validation fails.
151
+ OSError: If file writing fails.
152
+
153
+ Example:
154
+ >>> spec = ProtocolSpec(name="test", ...)
155
+ >>> generator = ScapyLayerGenerator(config)
156
+ >>> layer_path = generator.generate(
157
+ ... spec,
158
+ ... [b"\\x01\\x02\\x03"],
159
+ ... Path("test_layer.py")
160
+ ... )
161
+ """
162
+ # Validate spec
163
+ self._validate_spec(spec)
164
+
165
+ # Generate Python code with optional progress bar
166
+ if self.config.show_progress and len(sample_messages) > 100:
167
+ with tqdm(total=5, desc="Generating Scapy layer") as pbar:
168
+ pbar.set_description("Generating imports")
169
+ imports = self._generate_imports(spec)
170
+ pbar.update(1)
171
+
172
+ pbar.set_description("Generating CRC functions")
173
+ crc_code = self._generate_crc_functions(spec)
174
+ pbar.update(1)
175
+
176
+ pbar.set_description("Generating layer class")
177
+ layer_code = self._generate_layer_class(spec, sample_messages)
178
+ pbar.update(1)
179
+
180
+ pbar.set_description("Generating bind layers")
181
+ bind_code = self._generate_bind_layers(spec)
182
+ pbar.update(1)
183
+
184
+ pbar.set_description("Assembling code")
185
+ python_code = self._assemble_code(imports, crc_code, layer_code, bind_code)
186
+ pbar.update(1)
187
+ else:
188
+ imports = self._generate_imports(spec)
189
+ crc_code = self._generate_crc_functions(spec)
190
+ layer_code = self._generate_layer_class(spec, sample_messages)
191
+ bind_code = self._generate_bind_layers(spec)
192
+ python_code = self._assemble_code(imports, crc_code, layer_code, bind_code)
193
+
194
+ # Validate Python syntax
195
+ if not self._validate_python_syntax(python_code):
196
+ raise RuntimeError("Generated Python code has syntax errors")
197
+
198
+ # Write Python file
199
+ output_path.parent.mkdir(parents=True, exist_ok=True)
200
+ output_path.write_text(python_code, encoding="utf-8")
201
+ logger.info(f"Generated Scapy layer: {output_path}")
202
+
203
+ return output_path
204
+
205
+ def _validate_spec(self, spec: ProtocolSpec) -> None:
206
+ """Validate protocol specification.
207
+
208
+ Args:
209
+ spec: Protocol specification to validate.
210
+
211
+ Raises:
212
+ ValueError: If spec is invalid.
213
+ """
214
+ validate_protocol_spec(spec)
215
+
216
+ # Validate fields
217
+ for field in spec.fields:
218
+ if not field.name:
219
+ raise ValueError("Field name is required")
220
+ if field.field_type not in {
221
+ "uint8",
222
+ "uint16",
223
+ "uint32",
224
+ "bytes",
225
+ "string",
226
+ "constant",
227
+ "checksum",
228
+ }:
229
+ raise ValueError(f"Unsupported field type: {field.field_type}")
230
+
231
+ def _generate_imports(self, spec: ProtocolSpec) -> str:
232
+ """Generate import statements.
233
+
234
+ Args:
235
+ spec: Protocol specification.
236
+
237
+ Returns:
238
+ Python import statements.
239
+ """
240
+ imports = [
241
+ f'"""Scapy layer for {spec.name}.',
242
+ "",
243
+ f"Generated by Oscura on {datetime.now(UTC).isoformat()}",
244
+ "",
245
+ "Usage:",
246
+ f" >>> from {self._safe_module_name(spec.name)} import {self._safe_class_name(spec.name)}",
247
+ f" >>> pkt = {self._safe_class_name(spec.name)}()",
248
+ " >>> pkt.show()",
249
+ '"""',
250
+ "",
251
+ "from scapy.fields import (",
252
+ " ByteField,",
253
+ " ShortField,",
254
+ " IntField,",
255
+ " LEShortField,",
256
+ " LEIntField,",
257
+ " StrFixedLenField,",
258
+ " XByteField,",
259
+ " XShortField,",
260
+ " XIntField,",
261
+ ")",
262
+ "from scapy.packet import Packet, bind_layers",
263
+ "",
264
+ ]
265
+
266
+ return "\n".join(imports)
267
+
268
+ def _generate_crc_functions(self, spec: ProtocolSpec) -> str:
269
+ """Generate CRC validation functions.
270
+
271
+ Args:
272
+ spec: Protocol specification.
273
+
274
+ Returns:
275
+ Python CRC function code.
276
+ """
277
+ if not self.config.include_crc_validation:
278
+ return ""
279
+
280
+ if not spec.checksum_type or spec.checksum_type not in ("crc8", "crc16", "crc32"):
281
+ return ""
282
+
283
+ # Get CRC parameters if available
284
+ crc_info = getattr(spec, "crc_info", None)
285
+ if crc_info:
286
+ return self._generate_crc_function_from_params(crc_info)
287
+
288
+ # Default CRC implementations
289
+ if spec.checksum_type == "crc16":
290
+ return '''
291
+ def calculate_crc16(data: bytes) -> int:
292
+ """Calculate CRC-16-CCITT checksum.
293
+
294
+ Args:
295
+ data: Input data bytes.
296
+
297
+ Returns:
298
+ 16-bit CRC value.
299
+ """
300
+ crc = 0xFFFF
301
+ poly = 0x1021
302
+
303
+ for byte in data:
304
+ crc ^= byte << 8
305
+ for _ in range(8):
306
+ if crc & 0x8000:
307
+ crc = ((crc << 1) ^ poly) & 0xFFFF
308
+ else:
309
+ crc = (crc << 1) & 0xFFFF
310
+
311
+ return crc
312
+ '''
313
+ elif spec.checksum_type == "crc8":
314
+ return '''
315
+ def calculate_crc8(data: bytes) -> int:
316
+ """Calculate CRC-8 checksum.
317
+
318
+ Args:
319
+ data: Input data bytes.
320
+
321
+ Returns:
322
+ 8-bit CRC value.
323
+ """
324
+ crc = 0x00
325
+ poly = 0x07
326
+
327
+ for byte in data:
328
+ crc ^= byte
329
+ for _ in range(8):
330
+ if crc & 0x80:
331
+ crc = ((crc << 1) ^ poly) & 0xFF
332
+ else:
333
+ crc = (crc << 1) & 0xFF
334
+
335
+ return crc
336
+ '''
337
+ else: # crc32
338
+ return '''
339
+ def calculate_crc32(data: bytes) -> int:
340
+ """Calculate CRC-32 checksum.
341
+
342
+ Args:
343
+ data: Input data bytes.
344
+
345
+ Returns:
346
+ 32-bit CRC value.
347
+ """
348
+ crc = 0xFFFFFFFF
349
+ poly = 0x04C11DB7
350
+
351
+ for byte in data:
352
+ crc ^= byte << 24
353
+ for _ in range(8):
354
+ if crc & 0x80000000:
355
+ crc = ((crc << 1) ^ poly) & 0xFFFFFFFF
356
+ else:
357
+ crc = (crc << 1) & 0xFFFFFFFF
358
+
359
+ return crc ^ 0xFFFFFFFF
360
+ '''
361
+
362
+ def _generate_crc_function_from_params(self, crc_info: CRCParameters) -> str:
363
+ """Generate CRC function from CRCParameters.
364
+
365
+ Args:
366
+ crc_info: CRC parameters from reverse engineering.
367
+
368
+ Returns:
369
+ Python CRC function code.
370
+ """
371
+ width = crc_info.width
372
+ poly = crc_info.polynomial
373
+ init = crc_info.init
374
+ xor_out = crc_info.xor_out
375
+ mask = (1 << width) - 1
376
+
377
+ func_name = f"calculate_crc{width}"
378
+
379
+ lines = [
380
+ "",
381
+ f"def {func_name}(data: bytes) -> int:",
382
+ f' """Calculate CRC-{width} checksum (custom parameters).',
383
+ " ",
384
+ f" Polynomial: 0x{poly:0{width // 4}x}",
385
+ f" Init: 0x{init:0{width // 4}x}",
386
+ f" XorOut: 0x{xor_out:0{width // 4}x}",
387
+ f" ReflectIn: {crc_info.reflect_in}",
388
+ f" ReflectOut: {crc_info.reflect_out}",
389
+ " ",
390
+ " Args:",
391
+ " data: Input data bytes.",
392
+ " ",
393
+ " Returns:",
394
+ f" {width}-bit CRC value.",
395
+ ' """',
396
+ f" crc = 0x{init:0{width // 4}x}",
397
+ f" poly = 0x{poly:0{width // 4}x}",
398
+ f" mask = 0x{mask:0{width // 4}x}",
399
+ " ",
400
+ " for byte in data:",
401
+ ]
402
+
403
+ if crc_info.reflect_in:
404
+ lines.extend(
405
+ [
406
+ " # Reflect input byte",
407
+ " reflected = 0",
408
+ " for b in range(8):",
409
+ " if byte & (1 << b):",
410
+ " reflected |= 1 << (7 - b)",
411
+ " byte = reflected",
412
+ ]
413
+ )
414
+
415
+ lines.extend(
416
+ [
417
+ f" crc ^= byte << {width - 8}",
418
+ " for _ in range(8):",
419
+ f" if crc & 0x{1 << (width - 1):0{width // 4}x}:",
420
+ " crc = ((crc << 1) ^ poly) & mask",
421
+ " else:",
422
+ " crc = (crc << 1) & mask",
423
+ " ",
424
+ ]
425
+ )
426
+
427
+ if crc_info.reflect_out:
428
+ lines.extend(
429
+ [
430
+ " # Reflect output CRC",
431
+ " reflected = 0",
432
+ f" for b in range({width}):",
433
+ " if crc & (1 << b):",
434
+ f" reflected |= 1 << ({width - 1} - b)",
435
+ " crc = reflected",
436
+ " ",
437
+ ]
438
+ )
439
+
440
+ lines.extend([f" return crc ^ 0x{xor_out:0{width // 4}x}", ""])
441
+
442
+ return "\n".join(lines)
443
+
444
+ def _generate_layer_class(self, spec: ProtocolSpec, sample_messages: list[bytes]) -> str:
445
+ """Generate Scapy layer class definition.
446
+
447
+ Args:
448
+ spec: Protocol specification.
449
+ sample_messages: Sample messages for examples.
450
+
451
+ Returns:
452
+ Python class definition code.
453
+ """
454
+ class_name = self._safe_class_name(spec.name)
455
+ base_class = self.config.base_class
456
+
457
+ lines = [
458
+ f"class {class_name}({base_class}):",
459
+ f' """Scapy layer for {spec.name}.',
460
+ " ",
461
+ f" Baud Rate: {spec.baud_rate} bps",
462
+ f" Frame Format: {spec.frame_format}",
463
+ f" Sync Pattern: {spec.sync_pattern}",
464
+ f" Frame Length: {spec.frame_length if spec.frame_length else 'Variable'} bytes",
465
+ f" Checksum: {spec.checksum_type if spec.checksum_type else 'None'}",
466
+ " ",
467
+ ]
468
+
469
+ # Add examples if requested
470
+ if self.config.generate_examples and sample_messages:
471
+ lines.extend(
472
+ [
473
+ " Example:",
474
+ f" >>> pkt = {class_name}()",
475
+ " >>> pkt.show()",
476
+ ]
477
+ )
478
+ # Add first sample as example
479
+ if sample_messages:
480
+ first_sample = sample_messages[0]
481
+ lines.append(f" >>> raw_pkt = bytes.fromhex('{first_sample.hex()}')")
482
+ lines.append(f" >>> pkt = {class_name}(raw_pkt)")
483
+
484
+ lines.extend([' """', f' name = "{spec.name}"', " fields_desc = ["])
485
+
486
+ # Generate field definitions
487
+ for field in spec.fields:
488
+ field_def = self._generate_field_definition(field)
489
+ lines.append(f" {field_def},")
490
+
491
+ lines.extend([" ]", ""])
492
+
493
+ # Add post_build for CRC calculation if needed
494
+ if spec.checksum_type and spec.checksum_position is not None:
495
+ lines.extend(self._generate_post_build(spec))
496
+
497
+ # Add do_dissect for CRC validation if needed
498
+ if spec.checksum_type and self.config.include_crc_validation:
499
+ lines.extend(self._generate_do_dissect(spec))
500
+
501
+ return "\n".join(lines)
502
+
503
+ def _generate_field_definition(self, field: FieldSpec) -> str:
504
+ """Generate Scapy field definition.
505
+
506
+ Args:
507
+ field: Field specification.
508
+
509
+ Returns:
510
+ Python field definition code.
511
+ """
512
+ field_name = self._safe_field_name(field.name)
513
+ field_size = field.size if isinstance(field.size, int) else 1
514
+ endian = getattr(field, "endian", "big")
515
+
516
+ # Dispatch to type-specific handlers
517
+ if field.field_type == "uint8":
518
+ return self._gen_uint8_field(field_name, field.name)
519
+ elif field.field_type == "uint16":
520
+ return self._gen_uint16_field(field_name, field.name, endian)
521
+ elif field.field_type == "uint32":
522
+ return self._gen_uint32_field(field_name, field.name, endian)
523
+ elif field.field_type == "string":
524
+ return f'StrFixedLenField("{field_name}", b"", length={field_size})'
525
+ elif field.field_type in ("bytes", "constant"):
526
+ return self._gen_bytes_field(field_name, field, field_size)
527
+ else: # checksum
528
+ return self._gen_checksum_field(field_name, field_size)
529
+
530
+ def _gen_uint8_field(self, field_name: str, original_name: str) -> str:
531
+ """Generate uint8 field definition.
532
+
533
+ Args:
534
+ field_name: Safe field name.
535
+ original_name: Original field name.
536
+
537
+ Returns:
538
+ Field definition code.
539
+ """
540
+ if original_name == "checksum" or "crc" in original_name.lower():
541
+ return f'XByteField("{field_name}", 0)'
542
+ return f'ByteField("{field_name}", 0)'
543
+
544
+ def _gen_uint16_field(self, field_name: str, original_name: str, endian: str) -> str:
545
+ """Generate uint16 field definition.
546
+
547
+ Args:
548
+ field_name: Safe field name.
549
+ original_name: Original field name.
550
+ endian: Byte order (big/little).
551
+
552
+ Returns:
553
+ Field definition code.
554
+ """
555
+ if original_name == "checksum" or "crc" in original_name.lower():
556
+ return f'XShortField("{field_name}", 0)'
557
+ if endian == "little":
558
+ return f'LEShortField("{field_name}", 0)'
559
+ return f'ShortField("{field_name}", 0)'
560
+
561
+ def _gen_uint32_field(self, field_name: str, original_name: str, endian: str) -> str:
562
+ """Generate uint32 field definition.
563
+
564
+ Args:
565
+ field_name: Safe field name.
566
+ original_name: Original field name.
567
+ endian: Byte order (big/little).
568
+
569
+ Returns:
570
+ Field definition code.
571
+ """
572
+ if original_name == "checksum" or "crc" in original_name.lower():
573
+ return f'XIntField("{field_name}", 0)'
574
+ if endian == "little":
575
+ return f'LEIntField("{field_name}", 0)'
576
+ return f'IntField("{field_name}", 0)'
577
+
578
+ def _gen_bytes_field(self, field_name: str, field: FieldSpec, field_size: int) -> str:
579
+ """Generate bytes/constant field definition.
580
+
581
+ Args:
582
+ field_name: Safe field name.
583
+ field: Field specification.
584
+ field_size: Field size in bytes.
585
+
586
+ Returns:
587
+ Field definition code.
588
+ """
589
+ default_value = getattr(field, "value", None)
590
+ if default_value and isinstance(default_value, str):
591
+ default_bytes = bytes.fromhex(default_value)
592
+ return f'StrFixedLenField("{field_name}", {default_bytes!r}, length={field_size})'
593
+ return f'StrFixedLenField("{field_name}", b"\\x00" * {field_size}, length={field_size})'
594
+
595
+ def _gen_checksum_field(self, field_name: str, field_size: int) -> str:
596
+ """Generate checksum field definition.
597
+
598
+ Args:
599
+ field_name: Safe field name.
600
+ field_size: Field size in bytes.
601
+
602
+ Returns:
603
+ Field definition code.
604
+ """
605
+ if field_size == 1:
606
+ return f'XByteField("{field_name}", 0)'
607
+ elif field_size == 2:
608
+ return f'XShortField("{field_name}", 0)'
609
+ else:
610
+ return f'XIntField("{field_name}", 0)'
611
+
612
+ def _generate_post_build(self, spec: ProtocolSpec) -> list[str]:
613
+ """Generate post_build method for CRC calculation.
614
+
615
+ Args:
616
+ spec: Protocol specification.
617
+
618
+ Returns:
619
+ Python method code lines.
620
+ """
621
+ if not spec.checksum_type:
622
+ return []
623
+
624
+ width_map = {"crc8": 8, "crc16": 16, "crc32": 32}
625
+ width = width_map.get(spec.checksum_type, 16)
626
+
627
+ lines = [
628
+ " def post_build(self, pkt: bytes, pay: bytes) -> bytes:",
629
+ ' """Calculate and insert CRC checksum after packet build.',
630
+ " ",
631
+ " Args:",
632
+ " pkt: Built packet bytes.",
633
+ " pay: Payload bytes.",
634
+ " ",
635
+ " Returns:",
636
+ " Packet with calculated CRC.",
637
+ ' """',
638
+ ]
639
+
640
+ if spec.checksum_position == -1:
641
+ # CRC at end
642
+ crc_size = width // 8
643
+ lines.extend(
644
+ [
645
+ f" # Calculate CRC over packet minus last {crc_size} bytes",
646
+ f" data = pkt[:-{crc_size}]",
647
+ f" crc = calculate_crc{width}(data)",
648
+ " # Replace CRC field",
649
+ ]
650
+ )
651
+
652
+ if width == 8:
653
+ lines.append(" pkt = data + bytes([crc])")
654
+ elif width == 16:
655
+ lines.append(" pkt = data + crc.to_bytes(2, byteorder='big')")
656
+ else:
657
+ lines.append(" pkt = data + crc.to_bytes(4, byteorder='big')")
658
+
659
+ lines.extend([" return pkt + pay", ""])
660
+
661
+ return lines
662
+
663
+ def _generate_do_dissect(self, spec: ProtocolSpec) -> list[str]:
664
+ """Generate do_dissect method for CRC validation.
665
+
666
+ Args:
667
+ spec: Protocol specification.
668
+
669
+ Returns:
670
+ Python method code lines.
671
+ """
672
+ if not spec.checksum_type:
673
+ return []
674
+
675
+ width_map = {"crc8": 8, "crc16": 16, "crc32": 32}
676
+ width = width_map.get(spec.checksum_type, 16)
677
+ crc_size = width // 8
678
+
679
+ return [
680
+ " def do_dissect(self, s: bytes) -> bytes:",
681
+ ' """Dissect packet and validate CRC.',
682
+ " ",
683
+ " Args:",
684
+ " s: Raw packet bytes.",
685
+ " ",
686
+ " Returns:",
687
+ " Remaining bytes after dissection.",
688
+ ' """',
689
+ " # Standard dissection",
690
+ " s = super().do_dissect(s)",
691
+ " # Validate CRC",
692
+ f" data = bytes(self)[:-{crc_size}]",
693
+ f" calculated_crc = calculate_crc{width}(data)",
694
+ f" packet_crc = int.from_bytes(bytes(self)[-{crc_size}:], byteorder='big')",
695
+ " if calculated_crc != packet_crc:",
696
+ ' print(f"CRC mismatch: calculated={calculated_crc:04x}, '
697
+ 'packet={packet_crc:04x}")',
698
+ " return s",
699
+ "",
700
+ ]
701
+
702
+ def _generate_bind_layers(self, spec: ProtocolSpec) -> str:
703
+ """Generate bind_layers statements.
704
+
705
+ Args:
706
+ spec: Protocol specification.
707
+
708
+ Returns:
709
+ Python bind_layers code.
710
+ """
711
+ # For now, don't auto-bind to any layers
712
+ # Users can manually bind as needed
713
+ return f"# To bind this layer to another layer, use:\n# bind_layers(UDP, {self._safe_class_name(spec.name)}, dport=YOUR_PORT)\n"
714
+
715
+ def _assemble_code(self, imports: str, crc_code: str, layer_code: str, bind_code: str) -> str:
716
+ """Assemble complete Python module.
717
+
718
+ Args:
719
+ imports: Import statements.
720
+ crc_code: CRC function code.
721
+ layer_code: Layer class code.
722
+ bind_code: Bind layers code.
723
+
724
+ Returns:
725
+ Complete Python module code.
726
+ """
727
+ sections = [imports]
728
+
729
+ if crc_code:
730
+ sections.append(crc_code)
731
+
732
+ sections.extend([layer_code, bind_code])
733
+
734
+ return "\n".join(sections)
735
+
736
+ def _validate_python_syntax(self, python_code: str) -> bool:
737
+ """Validate Python syntax using ast.parse.
738
+
739
+ Args:
740
+ python_code: Python code to validate.
741
+
742
+ Returns:
743
+ True if syntax is valid, False if errors found.
744
+ """
745
+ try:
746
+ ast.parse(python_code)
747
+ logger.info("Python syntax validation passed")
748
+ return True
749
+ except SyntaxError as e:
750
+ logger.error(f"Python syntax error: {e}")
751
+ return False
752
+
753
+ def _safe_class_name(self, name: str) -> str:
754
+ """Convert protocol name to safe Python class name.
755
+
756
+ Args:
757
+ name: Protocol name.
758
+
759
+ Returns:
760
+ Safe Python class name (PascalCase).
761
+ """
762
+ # If name is already PascalCase (first letter uppercase, contains mix of upper/lower),
763
+ # preserve it as-is after removing non-alphanumeric chars
764
+ clean_name = "".join(c if c.isalnum() else "_" for c in name)
765
+
766
+ # Check if already PascalCase (starts with uppercase, has lowercase letters)
767
+ if clean_name and clean_name[0].isupper() and any(c.islower() for c in clean_name):
768
+ # Remove underscores but preserve case
769
+ return clean_name.replace("_", "")
770
+
771
+ # Otherwise convert to PascalCase
772
+ words = clean_name.split("_")
773
+ return "".join(word.capitalize() for word in words if word)
774
+
775
+ def _safe_field_name(self, name: str) -> str:
776
+ """Convert field name to safe Python identifier.
777
+
778
+ Args:
779
+ name: Field name.
780
+
781
+ Returns:
782
+ Safe Python field name (snake_case).
783
+ """
784
+ # Remove non-alphanumeric characters
785
+ clean_name = "".join(c if c.isalnum() else "_" for c in name)
786
+ # Convert to snake_case
787
+ return clean_name.lower()
788
+
789
+ def _safe_module_name(self, name: str) -> str:
790
+ """Convert protocol name to safe Python module name.
791
+
792
+ Args:
793
+ name: Protocol name.
794
+
795
+ Returns:
796
+ Safe Python module name (snake_case).
797
+ """
798
+ # Remove non-alphanumeric characters
799
+ clean_name = "".join(c if c.isalnum() else "_" for c in name)
800
+ # Convert to snake_case
801
+ return clean_name.lower()