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,596 @@
1
+ """Grammar-based test vector generation for protocol fuzzing and validation.
2
+
3
+ This module provides intelligent test vector generation from protocol specifications,
4
+ supporting coverage-based testing, edge case generation, and mutation-based fuzzing.
5
+
6
+ Example:
7
+ >>> from oscura.validation import GrammarTestGenerator, TestGenerationConfig
8
+ >>> from oscura.sessions import ProtocolSpec, FieldHypothesis
9
+ >>>
10
+ >>> # Define protocol spec
11
+ >>> spec = ProtocolSpec(
12
+ ... name="SimpleProtocol",
13
+ ... fields=[
14
+ ... FieldHypothesis("header", 0, 1, "constant", 0.99, {"value": 0xAA}),
15
+ ... FieldHypothesis("cmd", 1, 1, "data", 0.85),
16
+ ... FieldHypothesis("length", 2, 1, "data", 0.90),
17
+ ... FieldHypothesis("checksum", 3, 1, "checksum", 0.95),
18
+ ... ]
19
+ ... )
20
+ >>>
21
+ >>> # Generate comprehensive test suite
22
+ >>> config = TestGenerationConfig(strategy="all", num_tests=50)
23
+ >>> generator = GrammarTestGenerator(config)
24
+ >>> tests = generator.generate_tests(spec)
25
+ >>>
26
+ >>> # Export as PCAP
27
+ >>> generator.export_pcap(tests.valid_messages, Path("valid_tests.pcap"))
28
+ >>>
29
+ >>> # Export as pytest
30
+ >>> generator.export_pytest(tests.valid_messages, Path("test_protocol.py"))
31
+
32
+ References:
33
+ AFL Mutation Strategies: https://lcamtuf.coredump.cx/afl/technical_details.txt
34
+ Hypothesis Strategies: https://hypothesis.readthedocs.io/
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import random
40
+ from dataclasses import dataclass, field
41
+ from pathlib import Path
42
+ from typing import TYPE_CHECKING, Any, Literal
43
+
44
+ if TYPE_CHECKING:
45
+ from oscura.sessions.blackbox import FieldHypothesis, ProtocolSpec
46
+
47
+
48
+ @dataclass
49
+ class TestGenerationConfig:
50
+ """Configuration for test vector generation.
51
+
52
+ Attributes:
53
+ strategy: Generation strategy ("coverage", "fuzzing", "edge_cases", "all").
54
+ num_tests: Number of test vectors to generate.
55
+ include_valid: Include valid messages in output.
56
+ include_invalid: Include invalid messages in output.
57
+ mutate_checksums: Generate messages with corrupted checksums.
58
+ boundary_values: Generate boundary value test cases.
59
+ export_format: Export format ("pcap", "binary", "pytest").
60
+
61
+ Example:
62
+ >>> config = TestGenerationConfig(
63
+ ... strategy="coverage",
64
+ ... num_tests=100,
65
+ ... include_valid=True,
66
+ ... include_invalid=True
67
+ ... )
68
+ """
69
+
70
+ strategy: Literal["coverage", "fuzzing", "edge_cases", "all"] = "coverage"
71
+ num_tests: int = 100
72
+ include_valid: bool = True
73
+ include_invalid: bool = True
74
+ mutate_checksums: bool = True
75
+ boundary_values: bool = True
76
+ export_format: Literal["pcap", "binary", "pytest"] = "pcap"
77
+
78
+ def __post_init__(self) -> None:
79
+ """Validate configuration after initialization."""
80
+ if self.num_tests <= 0:
81
+ raise ValueError(f"num_tests must be positive, got {self.num_tests}")
82
+ if self.strategy not in {"coverage", "fuzzing", "edge_cases", "all"}:
83
+ raise ValueError(f"Invalid strategy: {self.strategy}")
84
+ if self.export_format not in {"pcap", "binary", "pytest"}:
85
+ raise ValueError(f"Invalid export_format: {self.export_format}")
86
+
87
+
88
+ @dataclass
89
+ class GeneratedTests:
90
+ """Container for generated test vectors.
91
+
92
+ Attributes:
93
+ valid_messages: Valid protocol messages.
94
+ invalid_messages: Invalid messages (corrupted checksums, bad lengths).
95
+ edge_cases: Boundary value test cases.
96
+ fuzzing_corpus: Mutation-based fuzzing inputs.
97
+ test_descriptions: Human-readable description for each test.
98
+ coverage_report: Coverage statistics (fields covered, value ranges).
99
+
100
+ Example:
101
+ >>> tests = GeneratedTests(
102
+ ... valid_messages=[b"\\xaa\\x01\\x00\\x12"],
103
+ ... invalid_messages=[b"\\xaa\\x01\\x00\\xff"],
104
+ ... test_descriptions=["Valid message", "Bad checksum"]
105
+ ... )
106
+ >>> print(f"Total tests: {len(tests.all_messages)}")
107
+ """
108
+
109
+ valid_messages: list[bytes] = field(default_factory=list)
110
+ invalid_messages: list[bytes] = field(default_factory=list)
111
+ edge_cases: list[bytes] = field(default_factory=list)
112
+ fuzzing_corpus: list[bytes] = field(default_factory=list)
113
+ test_descriptions: list[str] = field(default_factory=list)
114
+ coverage_report: dict[str, Any] = field(default_factory=dict)
115
+
116
+ @property
117
+ def all_messages(self) -> list[bytes]:
118
+ """Get all generated messages.
119
+
120
+ Returns:
121
+ Combined list of all message types.
122
+ """
123
+ return self.valid_messages + self.invalid_messages + self.edge_cases + self.fuzzing_corpus
124
+
125
+
126
+ class GrammarTestGenerator:
127
+ """Grammar-based test vector generator for protocol validation and fuzzing.
128
+
129
+ Generates comprehensive test suites from protocol specifications using
130
+ coverage-based generation, boundary value analysis, and mutation-based fuzzing.
131
+
132
+ Attributes:
133
+ config: Test generation configuration.
134
+
135
+ Example:
136
+ >>> config = TestGenerationConfig(strategy="coverage", num_tests=50)
137
+ >>> generator = GrammarTestGenerator(config)
138
+ >>> tests = generator.generate_tests(protocol_spec)
139
+ >>> print(f"Generated {len(tests.valid_messages)} valid tests")
140
+ """
141
+
142
+ def __init__(self, config: TestGenerationConfig) -> None:
143
+ """Initialize test generator.
144
+
145
+ Args:
146
+ config: Test generation configuration.
147
+ """
148
+ self.config = config
149
+ self._rng = random.Random(42) # Deterministic for reproducibility
150
+
151
+ def generate_tests(self, spec: ProtocolSpec) -> GeneratedTests:
152
+ """Generate comprehensive test suite from protocol specification.
153
+
154
+ Args:
155
+ spec: Protocol specification with field definitions.
156
+
157
+ Returns:
158
+ Generated test vectors with coverage report.
159
+
160
+ Example:
161
+ >>> tests = generator.generate_tests(protocol_spec)
162
+ >>> print(f"Valid: {len(tests.valid_messages)}")
163
+ >>> print(f"Invalid: {len(tests.invalid_messages)}")
164
+ """
165
+ result = GeneratedTests()
166
+
167
+ # Generate based on strategy
168
+ if self.config.strategy in {"coverage", "all"}:
169
+ if self.config.include_valid:
170
+ result.valid_messages = self._generate_valid_messages(spec)
171
+
172
+ if self.config.strategy in {"edge_cases", "all"}:
173
+ if self.config.boundary_values:
174
+ result.edge_cases = self._generate_edge_cases(spec)
175
+
176
+ if self.config.strategy in {"fuzzing", "all"}:
177
+ # Generate base messages first if needed
178
+ base_messages = result.valid_messages or self._generate_valid_messages(spec)
179
+ result.fuzzing_corpus = self._generate_fuzzing_corpus(spec, base_messages)
180
+
181
+ # Generate invalid messages with corrupted checksums
182
+ if self.config.include_invalid and self.config.mutate_checksums:
183
+ base_messages = result.valid_messages or self._generate_valid_messages(spec)
184
+ result.invalid_messages = self._corrupt_checksums(spec, base_messages)
185
+
186
+ # Generate test descriptions
187
+ result.test_descriptions = self._generate_descriptions(result, spec)
188
+
189
+ # Generate coverage report
190
+ result.coverage_report = self._generate_coverage_report(result, spec)
191
+
192
+ return result
193
+
194
+ def _generate_valid_messages(self, spec: ProtocolSpec) -> list[bytes]:
195
+ """Generate valid messages covering all field combinations.
196
+
197
+ Args:
198
+ spec: Protocol specification.
199
+
200
+ Returns:
201
+ List of valid protocol messages.
202
+
203
+ Example:
204
+ >>> messages = generator._generate_valid_messages(spec)
205
+ >>> all(len(msg) == spec_length for msg in messages)
206
+ True
207
+ """
208
+ messages: list[bytes] = []
209
+ num_messages = min(self.config.num_tests, 50) # Cap at 50 for coverage
210
+
211
+ for _ in range(num_messages):
212
+ msg = bytearray()
213
+
214
+ for field_def in spec.fields:
215
+ field_bytes = self._generate_field_value(field_def, valid=True)
216
+ msg.extend(field_bytes)
217
+
218
+ messages.append(bytes(msg))
219
+
220
+ return messages
221
+
222
+ def _generate_field_value(self, field_def: FieldHypothesis, valid: bool = True) -> bytes:
223
+ """Generate value for a single field.
224
+
225
+ Args:
226
+ field_def: Field definition.
227
+ valid: Generate valid value (True) or potentially invalid (False).
228
+
229
+ Returns:
230
+ Field value as bytes.
231
+
232
+ Example:
233
+ >>> field = FieldHypothesis("counter", 0, 1, "counter", 0.9)
234
+ >>> value = generator._generate_field_value(field)
235
+ >>> len(value) == 1
236
+ True
237
+ """
238
+ if field_def.field_type == "constant":
239
+ # Constants have fixed values
240
+ const_val = field_def.evidence.get("value", 0)
241
+ return self._pack_value(const_val, field_def.length)
242
+
243
+ if field_def.field_type == "counter":
244
+ # Counters increment
245
+ counter_val = self._rng.randint(0, (256**field_def.length) - 1)
246
+ return self._pack_value(counter_val, field_def.length)
247
+
248
+ if field_def.field_type == "checksum":
249
+ # Checksums are computed later (placeholder for now)
250
+ return b"\x00" * field_def.length
251
+
252
+ # Default: random data
253
+ return bytes(self._rng.randint(0, 255) for _ in range(field_def.length))
254
+
255
+ def _generate_edge_cases(self, spec: ProtocolSpec) -> list[bytes]:
256
+ """Generate boundary value test cases.
257
+
258
+ Args:
259
+ spec: Protocol specification.
260
+
261
+ Returns:
262
+ List of edge case messages (0x00, 0xFF, overflow, underflow).
263
+
264
+ Example:
265
+ >>> edge_cases = generator._generate_edge_cases(spec)
266
+ >>> any(b"\\xff" in msg for msg in edge_cases)
267
+ True
268
+ """
269
+ edge_cases: list[bytes] = []
270
+
271
+ # Boundary values: all 0x00, all 0xFF
272
+ msg_len = sum(f.length for f in spec.fields)
273
+ edge_cases.append(b"\x00" * msg_len)
274
+ edge_cases.append(b"\xff" * msg_len)
275
+
276
+ # Field-specific boundary values
277
+ for field_idx, field_def in enumerate(spec.fields):
278
+ if field_def.field_type in {"data", "counter"}:
279
+ # Min value (0)
280
+ msg = self._build_message_with_field_value(spec, field_idx, 0)
281
+ edge_cases.append(msg)
282
+
283
+ # Max value
284
+ max_val = (256**field_def.length) - 1
285
+ msg = self._build_message_with_field_value(spec, field_idx, max_val)
286
+ edge_cases.append(msg)
287
+
288
+ return edge_cases
289
+
290
+ def _generate_fuzzing_corpus(
291
+ self, spec: ProtocolSpec, base_messages: list[bytes]
292
+ ) -> list[bytes]:
293
+ """Generate mutation-based fuzzing corpus.
294
+
295
+ Args:
296
+ spec: Protocol specification.
297
+ base_messages: Valid messages to mutate.
298
+
299
+ Returns:
300
+ List of mutated messages for fuzzing.
301
+
302
+ Example:
303
+ >>> corpus = generator._generate_fuzzing_corpus(spec, [b"\\xaa\\x01\\x00"])
304
+ >>> len(corpus) > 0
305
+ True
306
+ """
307
+ corpus: list[bytes] = []
308
+ num_mutations = min(self.config.num_tests, 100)
309
+
310
+ for _ in range(num_mutations):
311
+ if not base_messages:
312
+ break
313
+
314
+ # Select random base message
315
+ base_msg = self._rng.choice(base_messages)
316
+ mutated = self._mutate_message(base_msg)
317
+ corpus.append(mutated)
318
+
319
+ return corpus
320
+
321
+ def _mutate_message(self, message: bytes) -> bytes:
322
+ """Apply random mutation to message (AFL-inspired).
323
+
324
+ Args:
325
+ message: Original message.
326
+
327
+ Returns:
328
+ Mutated message.
329
+
330
+ Mutations:
331
+ - Bit flip (flip single bit)
332
+ - Byte flip (flip entire byte)
333
+ - Byte insert (insert random byte)
334
+ - Byte delete (delete random byte)
335
+ - Arithmetic (increment/decrement value)
336
+
337
+ Example:
338
+ >>> original = b"\\xaa\\x55\\x01"
339
+ >>> mutated = generator._mutate_message(original)
340
+ >>> mutated != original
341
+ True
342
+ """
343
+ if not message:
344
+ return message
345
+
346
+ msg = bytearray(message)
347
+ mutation_type = self._rng.choice(
348
+ ["bit_flip", "byte_flip", "byte_insert", "byte_delete", "arithmetic"]
349
+ )
350
+
351
+ if mutation_type == "bit_flip":
352
+ # Flip random bit
353
+ pos = self._rng.randint(0, len(msg) - 1)
354
+ bit = self._rng.randint(0, 7)
355
+ msg[pos] ^= 1 << bit
356
+
357
+ elif mutation_type == "byte_flip":
358
+ # Flip entire byte
359
+ pos = self._rng.randint(0, len(msg) - 1)
360
+ msg[pos] ^= 0xFF
361
+
362
+ elif mutation_type == "byte_insert":
363
+ # Insert random byte
364
+ pos = self._rng.randint(0, len(msg))
365
+ msg.insert(pos, self._rng.randint(0, 255))
366
+
367
+ elif mutation_type == "byte_delete":
368
+ # Delete random byte (if message > 1 byte)
369
+ if len(msg) > 1:
370
+ pos = self._rng.randint(0, len(msg) - 1)
371
+ del msg[pos]
372
+
373
+ elif mutation_type == "arithmetic":
374
+ # Increment or decrement value
375
+ pos = self._rng.randint(0, len(msg) - 1)
376
+ delta = self._rng.choice([-1, 1])
377
+ msg[pos] = (msg[pos] + delta) % 256
378
+
379
+ return bytes(msg)
380
+
381
+ def _corrupt_checksums(self, spec: ProtocolSpec, messages: list[bytes]) -> list[bytes]:
382
+ """Generate messages with corrupted checksums.
383
+
384
+ Args:
385
+ spec: Protocol specification.
386
+ messages: Valid messages.
387
+
388
+ Returns:
389
+ Messages with invalid checksums.
390
+
391
+ Example:
392
+ >>> invalid = generator._corrupt_checksums(spec, valid_messages)
393
+ >>> len(invalid) > 0
394
+ True
395
+ """
396
+ corrupted: list[bytes] = []
397
+
398
+ # Find checksum field
399
+ checksum_field_idx = None
400
+ checksum_offset = 0
401
+ for idx, field_def in enumerate(spec.fields):
402
+ if field_def.field_type == "checksum":
403
+ checksum_field_idx = idx
404
+ break
405
+ checksum_offset += field_def.length
406
+
407
+ if checksum_field_idx is None:
408
+ return [] # No checksum field
409
+
410
+ # Corrupt checksums in subset of messages
411
+ num_corrupt = min(len(messages), 10)
412
+ for msg in messages[:num_corrupt]:
413
+ msg_arr = bytearray(msg)
414
+ checksum_len = spec.fields[checksum_field_idx].length
415
+
416
+ # XOR checksum with random value
417
+ for i in range(checksum_len):
418
+ msg_arr[checksum_offset + i] ^= self._rng.randint(1, 255)
419
+
420
+ corrupted.append(bytes(msg_arr))
421
+
422
+ return corrupted
423
+
424
+ def _build_message_with_field_value(
425
+ self, spec: ProtocolSpec, field_idx: int, value: int
426
+ ) -> bytes:
427
+ """Build message with specific field set to value.
428
+
429
+ Args:
430
+ spec: Protocol specification.
431
+ field_idx: Index of field to set.
432
+ value: Value to assign to field.
433
+
434
+ Returns:
435
+ Complete message with field set.
436
+
437
+ Example:
438
+ >>> msg = generator._build_message_with_field_value(spec, 1, 0xFF)
439
+ >>> len(msg) > 0
440
+ True
441
+ """
442
+ msg = bytearray()
443
+
444
+ for idx, field_def in enumerate(spec.fields):
445
+ if idx == field_idx:
446
+ msg.extend(self._pack_value(value, field_def.length))
447
+ else:
448
+ msg.extend(self._generate_field_value(field_def, valid=True))
449
+
450
+ return bytes(msg)
451
+
452
+ def _pack_value(self, value: int, length: int) -> bytes:
453
+ """Pack integer value into bytes (little-endian).
454
+
455
+ Args:
456
+ value: Integer value.
457
+ length: Number of bytes.
458
+
459
+ Returns:
460
+ Packed bytes.
461
+
462
+ Example:
463
+ >>> generator._pack_value(0x1234, 2)
464
+ b'\\x34\\x12'
465
+ """
466
+ return value.to_bytes(length, byteorder="little")
467
+
468
+ def _generate_descriptions(self, tests: GeneratedTests, spec: ProtocolSpec) -> list[str]:
469
+ """Generate human-readable descriptions for test vectors.
470
+
471
+ Args:
472
+ tests: Generated test vectors.
473
+ spec: Protocol specification.
474
+
475
+ Returns:
476
+ List of test descriptions.
477
+
478
+ Example:
479
+ >>> descriptions = generator._generate_descriptions(tests, spec)
480
+ >>> len(descriptions) == len(tests.all_messages)
481
+ True
482
+ """
483
+ descriptions: list[str] = []
484
+
485
+ for msg in tests.valid_messages:
486
+ descriptions.append(f"Valid {spec.name} message: {msg.hex()}")
487
+
488
+ for msg in tests.invalid_messages:
489
+ descriptions.append(f"Invalid {spec.name} (bad checksum): {msg.hex()}")
490
+
491
+ for msg in tests.edge_cases:
492
+ descriptions.append(f"Edge case {spec.name}: {msg.hex()}")
493
+
494
+ for msg in tests.fuzzing_corpus:
495
+ descriptions.append(f"Fuzzing input {spec.name}: {msg.hex()}")
496
+
497
+ return descriptions
498
+
499
+ def _generate_coverage_report(
500
+ self, tests: GeneratedTests, spec: ProtocolSpec
501
+ ) -> dict[str, Any]:
502
+ """Generate coverage statistics for test suite.
503
+
504
+ Args:
505
+ tests: Generated test vectors.
506
+ spec: Protocol specification.
507
+
508
+ Returns:
509
+ Coverage report with field coverage and value ranges.
510
+
511
+ Example:
512
+ >>> report = generator._generate_coverage_report(tests, spec)
513
+ >>> "fields_covered" in report
514
+ True
515
+ """
516
+ report: dict[str, Any] = {
517
+ "total_tests": len(tests.all_messages),
518
+ "valid_tests": len(tests.valid_messages),
519
+ "invalid_tests": len(tests.invalid_messages),
520
+ "edge_cases": len(tests.edge_cases),
521
+ "fuzzing_inputs": len(tests.fuzzing_corpus),
522
+ "fields_covered": len(spec.fields),
523
+ "protocol_name": spec.name,
524
+ }
525
+
526
+ return report
527
+
528
+ def export_pcap(self, messages: list[bytes], output: Path) -> None:
529
+ """Export messages as PCAP file (UDP packets).
530
+
531
+ Args:
532
+ messages: Protocol messages to export.
533
+ output: Output PCAP file path.
534
+
535
+ Example:
536
+ >>> generator.export_pcap(tests.valid_messages, Path("tests.pcap"))
537
+ """
538
+ try:
539
+ from scapy.all import ( # type: ignore[attr-defined]
540
+ IP,
541
+ UDP,
542
+ Ether,
543
+ wrpcap,
544
+ )
545
+ except ImportError as e:
546
+ raise ImportError(
547
+ "scapy is required for PCAP export. Install with: uv pip install scapy"
548
+ ) from e
549
+
550
+ packets = []
551
+ for msg in messages:
552
+ # Wrap in Ethernet/IP/UDP
553
+ pkt = Ether() / IP() / UDP(sport=12345, dport=54321) / msg
554
+ packets.append(pkt)
555
+
556
+ wrpcap(str(output), packets)
557
+
558
+ def export_pytest(self, messages: list[bytes], output: Path) -> None:
559
+ """Export as pytest parametrized test cases.
560
+
561
+ Args:
562
+ messages: Protocol messages to export.
563
+ output: Output Python test file path.
564
+
565
+ Example:
566
+ >>> generator.export_pytest(tests.valid_messages, Path("test_proto.py"))
567
+ """
568
+ test_code = [
569
+ '"""Generated protocol test vectors."""',
570
+ "",
571
+ "import pytest",
572
+ "",
573
+ "",
574
+ "@pytest.mark.parametrize(",
575
+ ' "message,expected_valid",',
576
+ " [",
577
+ ]
578
+
579
+ # Add test cases
580
+ for msg in messages:
581
+ hex_msg = msg.hex()
582
+ test_code.append(f' (bytes.fromhex("{hex_msg}"), True),')
583
+
584
+ test_code.extend(
585
+ [
586
+ " ],",
587
+ ")",
588
+ "def test_protocol_parsing(message, expected_valid):",
589
+ ' """Test protocol message parsing."""',
590
+ " # TODO: Implement parser validation (user should replace with actual parser)",
591
+ " assert isinstance(message, bytes)",
592
+ " assert len(message) > 0",
593
+ ]
594
+ )
595
+
596
+ output.write_text("\n".join(test_code))