oscura 0.5.1__py3-none-any.whl → 0.7.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 (497) 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/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.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))