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,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
+ )