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,904 @@
1
+ """Protocol grammar validator for detecting specification errors and inconsistencies.
2
+
3
+ This module provides comprehensive validation of protocol specifications to detect
4
+ grammar errors, field inconsistencies, dependency issues, and state machine problems.
5
+
6
+ Example:
7
+ >>> from oscura.validation import ProtocolGrammarValidator
8
+ >>> from oscura.sessions import ProtocolSpec, FieldHypothesis
9
+ >>>
10
+ >>> # Define protocol spec
11
+ >>> spec = ProtocolSpec(
12
+ ... name="MyProtocol",
13
+ ... fields=[
14
+ ... FieldHypothesis("header", 0, 1, "constant", 0.99, {"value": 0xAA}),
15
+ ... FieldHypothesis("length", 1, 1, "data", 0.90),
16
+ ... FieldHypothesis("payload", 2, 4, "data", 0.85),
17
+ ... FieldHypothesis("checksum", 6, 1, "checksum", 0.95, {"range": (0, 6)}),
18
+ ... ]
19
+ ... )
20
+ >>>
21
+ >>> # Validate protocol specification
22
+ >>> validator = ProtocolGrammarValidator()
23
+ >>> report = validator.validate(spec)
24
+ >>>
25
+ >>> if report.has_errors():
26
+ ... print("Validation failed:")
27
+ ... for error in report.errors:
28
+ ... print(f" {error.severity}: {error.message}")
29
+ ... else:
30
+ ... print("Protocol specification is valid!")
31
+ >>>
32
+ >>> # Export report
33
+ >>> report.export_json(Path("validation_report.json"))
34
+
35
+ References:
36
+ V0.6.0_COMPLETE_COMPREHENSIVE_PLAN.md: Feature 36 (Conformance Testing)
37
+ Formal Language Theory: Grammar validation and consistency checking
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ from dataclasses import dataclass, field
43
+ from enum import Enum
44
+ from pathlib import Path
45
+ from typing import TYPE_CHECKING, Any
46
+
47
+ if TYPE_CHECKING:
48
+ from oscura.sessions.blackbox import FieldHypothesis, ProtocolSpec
49
+
50
+
51
+ class ErrorSeverity(Enum):
52
+ """Severity level for validation errors.
53
+
54
+ Attributes:
55
+ ERROR: Must fix - specification is invalid.
56
+ WARNING: Should fix - potential issues or ambiguities.
57
+ INFO: Advisory - suggestions for improvement.
58
+ """
59
+
60
+ ERROR = "ERROR"
61
+ WARNING = "WARNING"
62
+ INFO = "INFO"
63
+
64
+
65
+ class ErrorType(Enum):
66
+ """Type of validation error.
67
+
68
+ Attributes:
69
+ FIELD_OVERLAP: Fields occupy overlapping byte ranges.
70
+ FIELD_GAP: Gap between consecutive fields.
71
+ INVALID_OFFSET: Field offset is negative or invalid.
72
+ INVALID_LENGTH: Field length is zero or negative.
73
+ LENGTH_MISMATCH: Length field value doesn't match referenced field.
74
+ CHECKSUM_RANGE: Checksum coverage range is invalid.
75
+ DUPLICATE_FIELD: Multiple fields with same name.
76
+ UNREACHABLE_STATE: State machine has unreachable states.
77
+ MISSING_TRANSITION: State machine has incomplete transitions.
78
+ AMBIGUOUS_GRAMMAR: Protocol grammar has ambiguities.
79
+ INVALID_DEPENDENCY: Field dependency cannot be resolved.
80
+ ALIGNMENT_WARNING: Field not aligned to expected boundary.
81
+ ENUM_DUPLICATE: Enum has duplicate values.
82
+ ENUM_GAP: Enum has missing values in range.
83
+ """
84
+
85
+ FIELD_OVERLAP = "FIELD_OVERLAP"
86
+ FIELD_GAP = "FIELD_GAP"
87
+ INVALID_OFFSET = "INVALID_OFFSET"
88
+ INVALID_LENGTH = "INVALID_LENGTH"
89
+ LENGTH_MISMATCH = "LENGTH_MISMATCH"
90
+ CHECKSUM_RANGE = "CHECKSUM_RANGE"
91
+ DUPLICATE_FIELD = "DUPLICATE_FIELD"
92
+ UNREACHABLE_STATE = "UNREACHABLE_STATE"
93
+ MISSING_TRANSITION = "MISSING_TRANSITION"
94
+ AMBIGUOUS_GRAMMAR = "AMBIGUOUS_GRAMMAR"
95
+ INVALID_DEPENDENCY = "INVALID_DEPENDENCY"
96
+ ALIGNMENT_WARNING = "ALIGNMENT_WARNING"
97
+ ENUM_DUPLICATE = "ENUM_DUPLICATE"
98
+ ENUM_GAP = "ENUM_GAP"
99
+
100
+
101
+ @dataclass
102
+ class ValidationError:
103
+ """Single validation error with context and suggestions.
104
+
105
+ Attributes:
106
+ error_type: Type of error encountered.
107
+ severity: Severity level (ERROR/WARNING/INFO).
108
+ field_name: Name of field causing error (if applicable).
109
+ message: Human-readable error message.
110
+ suggestion: Suggested fix for the error.
111
+ line_number: Line number in specification (if applicable).
112
+ context: Additional context information.
113
+
114
+ Example:
115
+ >>> error = ValidationError(
116
+ ... error_type=ErrorType.FIELD_OVERLAP,
117
+ ... severity=ErrorSeverity.ERROR,
118
+ ... field_name="checksum",
119
+ ... message="Field 'checksum' overlaps with 'payload' at byte 5",
120
+ ... suggestion="Move checksum field to byte 6 or reduce payload length"
121
+ ... )
122
+ """
123
+
124
+ error_type: ErrorType
125
+ severity: ErrorSeverity
126
+ field_name: str | None
127
+ message: str
128
+ suggestion: str | None = None
129
+ line_number: int | None = None
130
+ context: dict[str, Any] = field(default_factory=dict)
131
+
132
+
133
+ @dataclass
134
+ class ValidationReport:
135
+ """Comprehensive validation report with errors, warnings, and info.
136
+
137
+ Attributes:
138
+ errors: List of ERROR severity issues (must fix).
139
+ warnings: List of WARNING severity issues (should fix).
140
+ info: List of INFO severity messages (advisory).
141
+ protocol_name: Name of validated protocol.
142
+ total_fields: Total number of fields validated.
143
+ metadata: Additional validation metadata.
144
+
145
+ Example:
146
+ >>> report = ValidationReport(
147
+ ... errors=[],
148
+ ... warnings=[warning1, warning2],
149
+ ... info=[info1],
150
+ ... protocol_name="MyProtocol",
151
+ ... total_fields=4
152
+ ... )
153
+ >>> print(f"Valid: {not report.has_errors()}")
154
+ """
155
+
156
+ errors: list[ValidationError] = field(default_factory=list)
157
+ warnings: list[ValidationError] = field(default_factory=list)
158
+ info: list[ValidationError] = field(default_factory=list)
159
+ protocol_name: str = ""
160
+ total_fields: int = 0
161
+ metadata: dict[str, Any] = field(default_factory=dict)
162
+
163
+ def has_errors(self) -> bool:
164
+ """Check if report contains any errors.
165
+
166
+ Returns:
167
+ True if errors exist, False otherwise.
168
+
169
+ Example:
170
+ >>> report = ValidationReport(errors=[error1])
171
+ >>> report.has_errors()
172
+ True
173
+ """
174
+ return len(self.errors) > 0
175
+
176
+ def has_warnings(self) -> bool:
177
+ """Check if report contains any warnings.
178
+
179
+ Returns:
180
+ True if warnings exist, False otherwise.
181
+
182
+ Example:
183
+ >>> report = ValidationReport(warnings=[warning1])
184
+ >>> report.has_warnings()
185
+ True
186
+ """
187
+ return len(self.warnings) > 0
188
+
189
+ def all_issues(self) -> list[ValidationError]:
190
+ """Get all issues (errors + warnings + info).
191
+
192
+ Returns:
193
+ Combined list of all validation issues.
194
+
195
+ Example:
196
+ >>> issues = report.all_issues()
197
+ >>> print(f"Total issues: {len(issues)}")
198
+ """
199
+ return self.errors + self.warnings + self.info
200
+
201
+ def export_json(self, output: Path) -> None:
202
+ """Export validation report as JSON.
203
+
204
+ Args:
205
+ output: Output JSON file path.
206
+
207
+ Example:
208
+ >>> report.export_json(Path("validation.json"))
209
+ """
210
+ import json
211
+
212
+ data = {
213
+ "protocol_name": self.protocol_name,
214
+ "total_fields": self.total_fields,
215
+ "summary": {
216
+ "errors": len(self.errors),
217
+ "warnings": len(self.warnings),
218
+ "info": len(self.info),
219
+ "is_valid": not self.has_errors(),
220
+ },
221
+ "errors": [
222
+ {
223
+ "type": err.error_type.value,
224
+ "severity": err.severity.value,
225
+ "field": err.field_name,
226
+ "message": err.message,
227
+ "suggestion": err.suggestion,
228
+ "line": err.line_number,
229
+ "context": err.context,
230
+ }
231
+ for err in self.errors
232
+ ],
233
+ "warnings": [
234
+ {
235
+ "type": warn.error_type.value,
236
+ "severity": warn.severity.value,
237
+ "field": warn.field_name,
238
+ "message": warn.message,
239
+ "suggestion": warn.suggestion,
240
+ "line": warn.line_number,
241
+ "context": warn.context,
242
+ }
243
+ for warn in self.warnings
244
+ ],
245
+ "info": [
246
+ {
247
+ "type": i.error_type.value,
248
+ "severity": i.severity.value,
249
+ "field": i.field_name,
250
+ "message": i.message,
251
+ "suggestion": i.suggestion,
252
+ "line": i.line_number,
253
+ "context": i.context,
254
+ }
255
+ for i in self.info
256
+ ],
257
+ "metadata": self.metadata,
258
+ }
259
+
260
+ output.write_text(json.dumps(data, indent=2))
261
+
262
+ def export_text(self, output: Path) -> None:
263
+ """Export validation report as human-readable text.
264
+
265
+ Args:
266
+ output: Output text file path.
267
+
268
+ Example:
269
+ >>> report.export_text(Path("validation.txt"))
270
+ """
271
+ lines = [
272
+ f"Validation Report: {self.protocol_name}",
273
+ "=" * 80,
274
+ f"Total Fields: {self.total_fields}",
275
+ f"Errors: {len(self.errors)}",
276
+ f"Warnings: {len(self.warnings)}",
277
+ f"Info: {len(self.info)}",
278
+ f"Status: {'INVALID' if self.has_errors() else 'VALID'}",
279
+ "",
280
+ ]
281
+
282
+ if self.errors:
283
+ lines.extend(["", "ERRORS:", "-" * 80])
284
+ for err in self.errors:
285
+ lines.append(f"[{err.error_type.value}] {err.message}")
286
+ if err.field_name:
287
+ lines.append(f" Field: {err.field_name}")
288
+ if err.suggestion:
289
+ lines.append(f" Suggestion: {err.suggestion}")
290
+ lines.append("")
291
+
292
+ if self.warnings:
293
+ lines.extend(["", "WARNINGS:", "-" * 80])
294
+ for warn in self.warnings:
295
+ lines.append(f"[{warn.error_type.value}] {warn.message}")
296
+ if warn.field_name:
297
+ lines.append(f" Field: {warn.field_name}")
298
+ if warn.suggestion:
299
+ lines.append(f" Suggestion: {warn.suggestion}")
300
+ lines.append("")
301
+
302
+ if self.info:
303
+ lines.extend(["", "INFO:", "-" * 80])
304
+ for i in self.info:
305
+ lines.append(f"[{i.error_type.value}] {i.message}")
306
+ if i.field_name:
307
+ lines.append(f" Field: {i.field_name}")
308
+ if i.suggestion:
309
+ lines.append(f" Suggestion: {i.suggestion}")
310
+ lines.append("")
311
+
312
+ output.write_text("\n".join(lines))
313
+
314
+
315
+ class ProtocolGrammarValidator:
316
+ """Validates protocol specifications for consistency and correctness.
317
+
318
+ Performs comprehensive validation including:
319
+ - Field definition checking (overlaps, gaps, valid ranges)
320
+ - Length field consistency
321
+ - Checksum coverage validation
322
+ - Enum value checking
323
+ - Conditional field dependencies
324
+ - State machine completeness
325
+ - Grammar ambiguity detection
326
+
327
+ Example:
328
+ >>> validator = ProtocolGrammarValidator()
329
+ >>> report = validator.validate(protocol_spec)
330
+ >>> if report.has_errors():
331
+ ... print("Invalid specification")
332
+ >>> report.export_json(Path("report.json"))
333
+ """
334
+
335
+ def __init__(
336
+ self,
337
+ check_alignment: bool = True,
338
+ check_gaps: bool = True,
339
+ check_state_machine: bool = True,
340
+ ) -> None:
341
+ """Initialize grammar validator.
342
+
343
+ Args:
344
+ check_alignment: Enable byte alignment warnings (default: True).
345
+ check_gaps: Enable gap detection between fields (default: True).
346
+ check_state_machine: Enable state machine validation (default: True).
347
+
348
+ Example:
349
+ >>> validator = ProtocolGrammarValidator(check_gaps=False)
350
+ """
351
+ self.check_alignment = check_alignment
352
+ self.check_gaps = check_gaps
353
+ self.check_state_machine = check_state_machine
354
+
355
+ def validate(self, spec: ProtocolSpec) -> ValidationReport:
356
+ """Validate complete protocol specification.
357
+
358
+ Args:
359
+ spec: Protocol specification to validate.
360
+
361
+ Returns:
362
+ Validation report with errors, warnings, and info.
363
+
364
+ Example:
365
+ >>> report = validator.validate(spec)
366
+ >>> print(f"Errors: {len(report.errors)}")
367
+ """
368
+ report = ValidationReport(
369
+ protocol_name=spec.name,
370
+ total_fields=len(spec.fields),
371
+ )
372
+
373
+ # Validate field definitions
374
+ self._validate_field_definitions(spec, report)
375
+
376
+ # Validate field dependencies
377
+ self._validate_dependencies(spec, report)
378
+
379
+ # Validate checksums
380
+ self._validate_checksums(spec, report)
381
+
382
+ # Validate enums (from evidence)
383
+ self._validate_enums(spec, report)
384
+
385
+ # Validate state machine
386
+ if self.check_state_machine and spec.state_machine is not None:
387
+ self._validate_state_machine(spec, report)
388
+
389
+ # Add metadata
390
+ report.metadata["validator_config"] = {
391
+ "check_alignment": self.check_alignment,
392
+ "check_gaps": self.check_gaps,
393
+ "check_state_machine": self.check_state_machine,
394
+ }
395
+
396
+ return report
397
+
398
+ def _check_duplicate_names(self, spec: ProtocolSpec, report: ValidationReport) -> None:
399
+ """Check for duplicate field names.
400
+
401
+ Args:
402
+ spec: Protocol specification.
403
+ report: Validation report to populate.
404
+ """
405
+ field_names = [f.name for f in spec.fields]
406
+ seen_names: set[str] = set()
407
+ for name in field_names:
408
+ if name in seen_names:
409
+ report.errors.append(
410
+ ValidationError(
411
+ error_type=ErrorType.DUPLICATE_FIELD,
412
+ severity=ErrorSeverity.ERROR,
413
+ field_name=name,
414
+ message=f"Duplicate field name: '{name}'",
415
+ suggestion="Rename one of the duplicate fields to be unique",
416
+ )
417
+ )
418
+ seen_names.add(name)
419
+
420
+ def _check_field_basic_validity(
421
+ self, field_def: FieldHypothesis, report: ValidationReport
422
+ ) -> None:
423
+ """Check basic field validity (offset and length).
424
+
425
+ Args:
426
+ field_def: Field definition to check.
427
+ report: Validation report to populate.
428
+ """
429
+ if field_def.offset < 0:
430
+ report.errors.append(
431
+ ValidationError(
432
+ error_type=ErrorType.INVALID_OFFSET,
433
+ severity=ErrorSeverity.ERROR,
434
+ field_name=field_def.name,
435
+ message=f"Field '{field_def.name}' has invalid offset: {field_def.offset}",
436
+ suggestion="Offset must be >= 0",
437
+ context={"offset": field_def.offset},
438
+ )
439
+ )
440
+
441
+ if field_def.length <= 0:
442
+ report.errors.append(
443
+ ValidationError(
444
+ error_type=ErrorType.INVALID_LENGTH,
445
+ severity=ErrorSeverity.ERROR,
446
+ field_name=field_def.name,
447
+ message=f"Field '{field_def.name}' has invalid length: {field_def.length}",
448
+ suggestion="Length must be > 0",
449
+ context={"length": field_def.length},
450
+ )
451
+ )
452
+
453
+ def _check_field_overlap_and_gap(
454
+ self, field_def: FieldHypothesis, next_field: FieldHypothesis, report: ValidationReport
455
+ ) -> None:
456
+ """Check for overlaps and gaps between consecutive fields.
457
+
458
+ Args:
459
+ field_def: Current field.
460
+ next_field: Next field.
461
+ report: Validation report to populate.
462
+ """
463
+ current_end = field_def.offset + field_def.length
464
+ next_start = next_field.offset
465
+
466
+ if current_end > next_start:
467
+ report.errors.append(
468
+ ValidationError(
469
+ error_type=ErrorType.FIELD_OVERLAP,
470
+ severity=ErrorSeverity.ERROR,
471
+ field_name=field_def.name,
472
+ message=(
473
+ f"Field '{field_def.name}' (bytes {field_def.offset}-"
474
+ f"{current_end - 1}) overlaps with '{next_field.name}' "
475
+ f"(starts at byte {next_start})"
476
+ ),
477
+ suggestion=(
478
+ f"Move '{next_field.name}' to byte {current_end} or "
479
+ f"reduce '{field_def.name}' length"
480
+ ),
481
+ context={
482
+ "current_field": field_def.name,
483
+ "current_range": (field_def.offset, current_end - 1),
484
+ "next_field": next_field.name,
485
+ "next_offset": next_start,
486
+ },
487
+ )
488
+ )
489
+ elif self.check_gaps and current_end < next_start:
490
+ gap_size = next_start - current_end
491
+ report.warnings.append(
492
+ ValidationError(
493
+ error_type=ErrorType.FIELD_GAP,
494
+ severity=ErrorSeverity.WARNING,
495
+ field_name=field_def.name,
496
+ message=(
497
+ f"Gap of {gap_size} byte(s) between '{field_def.name}' "
498
+ f"and '{next_field.name}'"
499
+ ),
500
+ suggestion=("Add padding field or adjust offsets to eliminate gap"),
501
+ context={
502
+ "gap_start": current_end,
503
+ "gap_end": next_start - 1,
504
+ "gap_size": gap_size,
505
+ },
506
+ )
507
+ )
508
+
509
+ def _check_field_alignment(self, field_def: FieldHypothesis, report: ValidationReport) -> None:
510
+ """Check field alignment.
511
+
512
+ Args:
513
+ field_def: Field definition.
514
+ report: Validation report to populate.
515
+ """
516
+ if self.check_alignment and field_def.length in {2, 4, 8}:
517
+ if field_def.offset % field_def.length != 0:
518
+ report.info.append(
519
+ ValidationError(
520
+ error_type=ErrorType.ALIGNMENT_WARNING,
521
+ severity=ErrorSeverity.INFO,
522
+ field_name=field_def.name,
523
+ message=(
524
+ f"Field '{field_def.name}' ({field_def.length}-byte) is not "
525
+ f"aligned to {field_def.length}-byte boundary (offset: "
526
+ f"{field_def.offset})"
527
+ ),
528
+ suggestion=(
529
+ f"Consider aligning to offset "
530
+ f"{(field_def.offset // field_def.length + 1) * field_def.length}"
531
+ ),
532
+ context={
533
+ "offset": field_def.offset,
534
+ "alignment": field_def.length,
535
+ },
536
+ )
537
+ )
538
+
539
+ def _validate_field_definitions(self, spec: ProtocolSpec, report: ValidationReport) -> None:
540
+ """Validate field definitions for overlaps, gaps, and invalid ranges.
541
+
542
+ Args:
543
+ spec: Protocol specification.
544
+ report: Validation report to populate.
545
+ """
546
+ # Check for duplicate field names
547
+ self._check_duplicate_names(spec, report)
548
+
549
+ # Sort fields by offset for sequential validation
550
+ sorted_fields = sorted(spec.fields, key=lambda f: f.offset)
551
+
552
+ for i, field_def in enumerate(sorted_fields):
553
+ # Check basic validity
554
+ self._check_field_basic_validity(field_def, report)
555
+
556
+ # Check overlap and gap with next field
557
+ if i < len(sorted_fields) - 1:
558
+ self._check_field_overlap_and_gap(field_def, sorted_fields[i + 1], report)
559
+
560
+ # Check alignment
561
+ self._check_field_alignment(field_def, report)
562
+
563
+ def _validate_dependencies(self, spec: ProtocolSpec, report: ValidationReport) -> None:
564
+ """Validate dependencies between fields (length fields, etc.).
565
+
566
+ Args:
567
+ spec: Protocol specification.
568
+ report: Validation report to populate.
569
+ """
570
+ # Build field name -> field mapping
571
+ field_map = {f.name: f for f in spec.fields}
572
+
573
+ for field_def in spec.fields:
574
+ # Check if field has dependency in evidence
575
+ if "depends_on" in field_def.evidence:
576
+ dep_name = field_def.evidence["depends_on"]
577
+ if dep_name not in field_map:
578
+ report.errors.append(
579
+ ValidationError(
580
+ error_type=ErrorType.INVALID_DEPENDENCY,
581
+ severity=ErrorSeverity.ERROR,
582
+ field_name=field_def.name,
583
+ message=(
584
+ f"Field '{field_def.name}' depends on non-existent field "
585
+ f"'{dep_name}'"
586
+ ),
587
+ suggestion=f"Add field '{dep_name}' or remove dependency",
588
+ context={"dependency": dep_name},
589
+ )
590
+ )
591
+
592
+ # Check length field references
593
+ if "references" in field_def.evidence:
594
+ ref_name = field_def.evidence["references"]
595
+ if ref_name not in field_map:
596
+ report.errors.append(
597
+ ValidationError(
598
+ error_type=ErrorType.INVALID_DEPENDENCY,
599
+ severity=ErrorSeverity.ERROR,
600
+ field_name=field_def.name,
601
+ message=(
602
+ f"Length field '{field_def.name}' references non-existent "
603
+ f"field '{ref_name}'"
604
+ ),
605
+ suggestion=f"Add field '{ref_name}' or remove reference",
606
+ context={"reference": ref_name},
607
+ )
608
+ )
609
+
610
+ def _validate_checksums(self, spec: ProtocolSpec, report: ValidationReport) -> None:
611
+ """Validate checksum field coverage ranges.
612
+
613
+ Args:
614
+ spec: Protocol specification.
615
+ report: Validation report to populate.
616
+ """
617
+ for field_def in spec.fields:
618
+ if field_def.field_type == "checksum":
619
+ # Check if range is specified in evidence
620
+ if "range" in field_def.evidence:
621
+ range_tuple = field_def.evidence["range"]
622
+ if not isinstance(range_tuple, (list, tuple)) or len(range_tuple) != 2:
623
+ report.errors.append(
624
+ ValidationError(
625
+ error_type=ErrorType.CHECKSUM_RANGE,
626
+ severity=ErrorSeverity.ERROR,
627
+ field_name=field_def.name,
628
+ message=(
629
+ f"Checksum '{field_def.name}' has invalid range format: "
630
+ f"{range_tuple}"
631
+ ),
632
+ suggestion="Range must be (start_byte, end_byte) tuple",
633
+ context={"range": range_tuple},
634
+ )
635
+ )
636
+ continue
637
+
638
+ start_byte, end_byte = range_tuple
639
+
640
+ # Validate range bounds
641
+ if start_byte < 0 or end_byte < start_byte:
642
+ report.errors.append(
643
+ ValidationError(
644
+ error_type=ErrorType.CHECKSUM_RANGE,
645
+ severity=ErrorSeverity.ERROR,
646
+ field_name=field_def.name,
647
+ message=(
648
+ f"Checksum '{field_def.name}' has invalid range: "
649
+ f"({start_byte}, {end_byte})"
650
+ ),
651
+ suggestion="Range must be (start >= 0, end >= start)",
652
+ context={"range": (start_byte, end_byte)},
653
+ )
654
+ )
655
+
656
+ # Check if checksum covers itself (should not)
657
+ if start_byte <= field_def.offset < end_byte:
658
+ report.warnings.append(
659
+ ValidationError(
660
+ error_type=ErrorType.CHECKSUM_RANGE,
661
+ severity=ErrorSeverity.WARNING,
662
+ field_name=field_def.name,
663
+ message=(
664
+ f"Checksum '{field_def.name}' at byte {field_def.offset} "
665
+ f"is within its own coverage range ({start_byte}, {end_byte})"
666
+ ),
667
+ suggestion=("Checksum should typically not cover its own location"),
668
+ context={
669
+ "checksum_offset": field_def.offset,
670
+ "coverage_range": (start_byte, end_byte),
671
+ },
672
+ )
673
+ )
674
+
675
+ def _check_enum_duplicates(
676
+ self,
677
+ field_def: FieldHypothesis,
678
+ enum_values: dict[str, Any],
679
+ report: ValidationReport,
680
+ ) -> None:
681
+ """Check for duplicate enum values.
682
+
683
+ Args:
684
+ field_def: Field definition
685
+ enum_values: Enum values dict
686
+ report: Validation report to populate
687
+ """
688
+ value_to_names: dict[Any, list[str]] = {}
689
+ for name, value in enum_values.items():
690
+ if value not in value_to_names:
691
+ value_to_names[value] = []
692
+ value_to_names[value].append(name)
693
+
694
+ for value, names in value_to_names.items():
695
+ if len(names) > 1:
696
+ report.warnings.append(
697
+ ValidationError(
698
+ error_type=ErrorType.ENUM_DUPLICATE,
699
+ severity=ErrorSeverity.WARNING,
700
+ field_name=field_def.name,
701
+ message=(
702
+ f"Enum in '{field_def.name}' has duplicate value {value} "
703
+ f"for names: {', '.join(names)}"
704
+ ),
705
+ suggestion="Ensure each enum value is unique",
706
+ context={"value": value, "names": names},
707
+ )
708
+ )
709
+
710
+ def _check_enum_gaps(
711
+ self,
712
+ field_def: FieldHypothesis,
713
+ enum_values: dict[str, Any],
714
+ report: ValidationReport,
715
+ ) -> None:
716
+ """Check for gaps in sequential enums.
717
+
718
+ Args:
719
+ field_def: Field definition
720
+ enum_values: Enum values dict
721
+ report: Validation report to populate
722
+ """
723
+ if not all(isinstance(v, int) for v in enum_values.values()):
724
+ return
725
+
726
+ int_values = sorted([v for v in enum_values.values() if isinstance(v, int)])
727
+ if not int_values:
728
+ return
729
+
730
+ min_val, max_val = int_values[0], int_values[-1]
731
+ expected_range = set(range(min_val, max_val + 1))
732
+ actual_set = set(int_values)
733
+ missing = expected_range - actual_set
734
+
735
+ if missing and len(missing) <= 5: # Only report small gaps
736
+ report.info.append(
737
+ ValidationError(
738
+ error_type=ErrorType.ENUM_GAP,
739
+ severity=ErrorSeverity.INFO,
740
+ field_name=field_def.name,
741
+ message=(
742
+ f"Enum in '{field_def.name}' has gaps in value range: "
743
+ f"missing {sorted(missing)}"
744
+ ),
745
+ suggestion="Add missing enum values or verify range",
746
+ context={"missing_values": sorted(missing)},
747
+ )
748
+ )
749
+
750
+ def _validate_enums(self, spec: ProtocolSpec, report: ValidationReport) -> None:
751
+ """Validate enum values from field evidence.
752
+
753
+ Args:
754
+ spec: Protocol specification.
755
+ report: Validation report to populate.
756
+ """
757
+ for field_def in spec.fields:
758
+ if "enum_values" not in field_def.evidence:
759
+ continue
760
+
761
+ enum_values = field_def.evidence["enum_values"]
762
+ if not isinstance(enum_values, dict):
763
+ continue
764
+
765
+ # Validate enum properties
766
+ self._check_enum_duplicates(field_def, enum_values, report)
767
+ self._check_enum_gaps(field_def, enum_values, report)
768
+
769
+ def _validate_state_machine(self, spec: ProtocolSpec, report: ValidationReport) -> None:
770
+ """Validate state machine completeness and reachability.
771
+
772
+ Args:
773
+ spec: Protocol specification.
774
+ report: Validation report to populate.
775
+ """
776
+ sm = spec.state_machine
777
+ if sm is None:
778
+ return
779
+
780
+ # Verify state machine structure
781
+ if not self._check_state_machine_format(sm, report):
782
+ return
783
+
784
+ states = sm.states
785
+ transitions = sm.transitions
786
+
787
+ # Check state reachability
788
+ self._check_unreachable_states(sm, states, transitions, report)
789
+
790
+ # Check for dead-end states
791
+ self._check_dead_end_states(sm, states, transitions, report)
792
+
793
+ def _check_state_machine_format(self, sm: Any, report: ValidationReport) -> bool:
794
+ """Check if state machine has required attributes.
795
+
796
+ Args:
797
+ sm: State machine object.
798
+ report: Validation report to populate with errors.
799
+
800
+ Returns:
801
+ True if format is valid, False otherwise.
802
+ """
803
+ if not hasattr(sm, "states") or not hasattr(sm, "transitions"):
804
+ report.warnings.append(
805
+ ValidationError(
806
+ error_type=ErrorType.AMBIGUOUS_GRAMMAR,
807
+ severity=ErrorSeverity.WARNING,
808
+ field_name=None,
809
+ message="State machine format not recognized, skipping validation",
810
+ suggestion="Ensure state machine has 'states' and 'transitions' attributes",
811
+ )
812
+ )
813
+ return False
814
+ return True
815
+
816
+ def _check_unreachable_states(
817
+ self, sm: Any, states: Any, transitions: Any, report: ValidationReport
818
+ ) -> None:
819
+ """Find and report unreachable states.
820
+
821
+ Args:
822
+ sm: State machine with initial_state attribute.
823
+ states: Collection of all states.
824
+ transitions: Collection of transitions.
825
+ report: Validation report to populate.
826
+ """
827
+ if not hasattr(sm, "initial_state"):
828
+ return
829
+
830
+ # Build reachability set using BFS
831
+ reachable = self._find_reachable_states(sm.initial_state, transitions)
832
+
833
+ # Report unreachable states
834
+ unreachable = set(states) - reachable
835
+ for state in unreachable:
836
+ report.warnings.append(
837
+ ValidationError(
838
+ error_type=ErrorType.UNREACHABLE_STATE,
839
+ severity=ErrorSeverity.WARNING,
840
+ field_name=None,
841
+ message=f"State '{state}' is unreachable from initial state",
842
+ suggestion="Add transition to this state or remove it",
843
+ context={"state": state, "initial_state": sm.initial_state},
844
+ )
845
+ )
846
+
847
+ def _find_reachable_states(self, initial_state: Any, transitions: Any) -> set[Any]:
848
+ """Find all states reachable from initial state using BFS.
849
+
850
+ Args:
851
+ initial_state: Starting state.
852
+ transitions: Collection of transitions with source/target attributes.
853
+
854
+ Returns:
855
+ Set of reachable states.
856
+ """
857
+ reachable = {initial_state}
858
+ queue = [initial_state]
859
+
860
+ while queue:
861
+ current = queue.pop(0)
862
+ for trans in transitions:
863
+ if hasattr(trans, "source") and trans.source == current:
864
+ target = trans.target if hasattr(trans, "target") else None
865
+ if target and target not in reachable:
866
+ reachable.add(target)
867
+ queue.append(target)
868
+
869
+ return reachable
870
+
871
+ def _check_dead_end_states(
872
+ self, sm: Any, states: Any, transitions: Any, report: ValidationReport
873
+ ) -> None:
874
+ """Find and report states with no outgoing transitions.
875
+
876
+ Args:
877
+ sm: State machine with optional final_states attribute.
878
+ states: Collection of all states.
879
+ transitions: Collection of transitions.
880
+ report: Validation report to populate.
881
+ """
882
+ # Identify states with outgoing transitions
883
+ states_with_transitions = {
884
+ trans.source for trans in transitions if hasattr(trans, "source")
885
+ }
886
+
887
+ # Get designated final states
888
+ final_states = set()
889
+ if hasattr(sm, "final_states"):
890
+ final_states = set(sm.final_states)
891
+
892
+ # Report dead ends (states without transitions and not marked as final)
893
+ for state in states:
894
+ if state not in states_with_transitions and state not in final_states:
895
+ report.warnings.append(
896
+ ValidationError(
897
+ error_type=ErrorType.MISSING_TRANSITION,
898
+ severity=ErrorSeverity.WARNING,
899
+ field_name=None,
900
+ message=f"State '{state}' has no outgoing transitions (dead end)",
901
+ suggestion="Add transitions from this state or mark as final state",
902
+ context={"state": state},
903
+ )
904
+ )