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,513 @@
1
+ """Kaitai Struct (.ksy) generator from ProtocolSpec.
2
+
3
+ This module generates valid Kaitai Struct format definitions (.ksy files)
4
+ from ProtocolSpec objects (from reverse engineering workflows). The generated
5
+ .ksy files can be compiled with kaitai-struct-compiler to create parsers in
6
+ 50+ programming languages.
7
+
8
+ Features:
9
+ - Generate valid .ksy files from ProtocolSpec
10
+ - Support all field types (u1, u2, u4, str, enum, etc.)
11
+ - Handle endianness (be/le)
12
+ - Generate enums and constants
13
+ - Add metadata (id, endian, doc)
14
+ - Validate .ksy syntax if kaitai-struct-compiler available
15
+ - Include usage documentation
16
+
17
+ Example:
18
+ >>> from oscura.export.kaitai_struct import (
19
+ ... KaitaiStructGenerator,
20
+ ... KaitaiConfig
21
+ ... )
22
+ >>> from oscura.workflows.reverse_engineering import ProtocolSpec, FieldSpec
23
+ >>> spec = ProtocolSpec(
24
+ ... name="MyProtocol",
25
+ ... baud_rate=115200,
26
+ ... frame_format="8N1",
27
+ ... sync_pattern="aa55",
28
+ ... frame_length=10,
29
+ ... fields=[
30
+ ... FieldSpec(name="sync", offset=0, size=2, field_type="bytes"),
31
+ ... FieldSpec(name="length", offset=2, size=1, field_type="uint8"),
32
+ ... FieldSpec(name="data", offset=3, size=4, field_type="bytes"),
33
+ ... ],
34
+ ... checksum_type=None,
35
+ ... checksum_position=None,
36
+ ... confidence=0.95
37
+ ... )
38
+ >>> config = KaitaiConfig(protocol_id="my_protocol", endian="le")
39
+ >>> generator = KaitaiStructGenerator(config)
40
+ >>> ksy_path = generator.generate(spec, Path("my_protocol.ksy"))
41
+
42
+ Installation:
43
+ Install kaitai-struct-compiler for syntax validation and compilation:
44
+ - Linux: apt-get install kaitai-struct-compiler
45
+ - macOS: brew install kaitai-struct-compiler
46
+ - Windows: Download from https://kaitai.io/#download
47
+
48
+ Usage:
49
+ After generating .ksy file, compile it to target language:
50
+ $ ksc my_protocol.ksy -t python
51
+ $ ksc my_protocol.ksy -t cpp_stl
52
+ $ ksc my_protocol.ksy -t java
53
+
54
+ References:
55
+ - Kaitai Struct format: http://doc.kaitai.io/user_guide.html
56
+ - KSY format reference: http://doc.kaitai.io/ksy_reference.html
57
+ """
58
+
59
+ from __future__ import annotations
60
+
61
+ import logging
62
+ import subprocess
63
+ from dataclasses import dataclass
64
+ from pathlib import Path
65
+ from typing import TYPE_CHECKING, Any
66
+
67
+ import yaml
68
+
69
+ from oscura.utils.validation import validate_protocol_spec
70
+
71
+ if TYPE_CHECKING:
72
+ from oscura.workflows.reverse_engineering import ProtocolSpec
73
+
74
+ __all__ = ["KaitaiConfig", "KaitaiStructGenerator"]
75
+
76
+ logger = logging.getLogger(__name__)
77
+
78
+
79
+ @dataclass
80
+ class KaitaiConfig:
81
+ """Configuration for Kaitai Struct generation.
82
+
83
+ Attributes:
84
+ protocol_id: Protocol ID for .ksy file (lowercase, underscores).
85
+ endian: Default endianness ("le" or "be").
86
+ include_doc: Include documentation strings in .ksy.
87
+ validate_syntax: Validate .ksy syntax if ksc is available.
88
+ """
89
+
90
+ protocol_id: str
91
+ endian: str = "le"
92
+ include_doc: bool = True
93
+ validate_syntax: bool = True
94
+
95
+
96
+ class KaitaiStructGenerator:
97
+ """Generate Kaitai Struct format definitions from ProtocolSpec.
98
+
99
+ This class converts ProtocolSpec objects (from reverse engineering workflows)
100
+ into Kaitai Struct (.ksy) format files that can be compiled to parsers in
101
+ 50+ programming languages using kaitai-struct-compiler.
102
+
103
+ Features:
104
+ - All field types (uint8, uint16, uint32, bytes, string, enum)
105
+ - Endianness handling (big-endian, little-endian)
106
+ - Enum generation
107
+ - Constant field validation
108
+ - Checksum field marking
109
+ - YAML syntax validation
110
+
111
+ Example:
112
+ >>> config = KaitaiConfig(protocol_id="my_protocol", endian="le")
113
+ >>> generator = KaitaiStructGenerator(config)
114
+ >>> ksy_path = generator.generate(spec, Path("my_protocol.ksy"))
115
+ >>> # Compile to Python: ksc my_protocol.ksy -t python
116
+ """
117
+
118
+ def __init__(self, config: KaitaiConfig) -> None:
119
+ """Initialize Kaitai Struct generator.
120
+
121
+ Args:
122
+ config: Kaitai Struct generation configuration.
123
+ """
124
+ self.config = config
125
+
126
+ def generate(
127
+ self,
128
+ spec: ProtocolSpec,
129
+ output_path: Path,
130
+ ) -> Path:
131
+ """Generate Kaitai Struct .ksy file from ProtocolSpec.
132
+
133
+ Args:
134
+ spec: Protocol specification from reverse engineering.
135
+ output_path: Path for output .ksy file.
136
+
137
+ Returns:
138
+ Path to generated .ksy file.
139
+
140
+ Raises:
141
+ ValueError: If spec is invalid or has missing required fields.
142
+ RuntimeError: If .ksy syntax validation fails.
143
+ OSError: If file writing fails.
144
+
145
+ Example:
146
+ >>> spec = ProtocolSpec(name="test", ...)
147
+ >>> generator = KaitaiStructGenerator(config)
148
+ >>> ksy_path = generator.generate(spec, Path("test.ksy"))
149
+ >>> # Generated .ksy can be compiled with ksc
150
+ """
151
+ # Validate spec
152
+ self._validate_spec(spec)
153
+
154
+ # Generate .ksy structure
155
+ ksy_data = self._generate_ksy_structure(spec)
156
+
157
+ # Convert to YAML
158
+ yaml_content = self._generate_yaml(ksy_data)
159
+
160
+ # Validate syntax if requested
161
+ if self.config.validate_syntax and not self._validate_ksy_syntax(yaml_content):
162
+ raise RuntimeError("Generated .ksy file has syntax errors")
163
+
164
+ # Write .ksy file
165
+ output_path.parent.mkdir(parents=True, exist_ok=True)
166
+ output_path.write_text(yaml_content, encoding="utf-8")
167
+ logger.info(f"Generated Kaitai Struct .ksy file: {output_path}")
168
+
169
+ return output_path
170
+
171
+ def _validate_spec(self, spec: ProtocolSpec) -> None:
172
+ """Validate protocol specification.
173
+
174
+ Args:
175
+ spec: Protocol specification to validate.
176
+
177
+ Raises:
178
+ ValueError: If spec is invalid.
179
+ """
180
+ validate_protocol_spec(spec)
181
+
182
+ # Validate fields
183
+ for field in spec.fields:
184
+ if not field.name:
185
+ raise ValueError("Field name is required")
186
+ if field.field_type not in {
187
+ "uint8",
188
+ "uint16",
189
+ "uint32",
190
+ "bytes",
191
+ "string",
192
+ "constant",
193
+ "checksum",
194
+ }:
195
+ raise ValueError(f"Unsupported field type: {field.field_type}")
196
+
197
+ # Validate protocol_id format
198
+ if not self.config.protocol_id:
199
+ raise ValueError("protocol_id is required")
200
+ if not self.config.protocol_id.replace("_", "").isalnum():
201
+ raise ValueError("protocol_id must be alphanumeric with underscores")
202
+ if self.config.protocol_id != self.config.protocol_id.lower():
203
+ raise ValueError("protocol_id must be lowercase")
204
+
205
+ # Validate endianness
206
+ if self.config.endian not in {"le", "be"}:
207
+ raise ValueError("endian must be 'le' or 'be'")
208
+
209
+ def _generate_ksy_structure(self, spec: ProtocolSpec) -> dict[str, Any]:
210
+ """Generate complete .ksy structure as dictionary.
211
+
212
+ Args:
213
+ spec: Protocol specification.
214
+
215
+ Returns:
216
+ Complete .ksy structure as nested dictionary.
217
+ """
218
+ ksy: dict[str, Any] = {
219
+ "meta": self._generate_meta(spec),
220
+ "seq": self._generate_sequence(spec),
221
+ }
222
+
223
+ # Add enums if any fields have enum values
224
+ enums = self._generate_enums(spec)
225
+ if enums:
226
+ ksy["enums"] = enums
227
+
228
+ # Add doc if requested
229
+ if self.config.include_doc:
230
+ ksy["doc"] = self._generate_documentation(spec)
231
+
232
+ return ksy
233
+
234
+ def _generate_meta(self, spec: ProtocolSpec) -> dict[str, Any]:
235
+ """Generate meta section of .ksy file.
236
+
237
+ Args:
238
+ spec: Protocol specification.
239
+
240
+ Returns:
241
+ Meta section dictionary.
242
+ """
243
+ meta: dict[str, Any] = {
244
+ "id": self.config.protocol_id,
245
+ "endian": self.config.endian,
246
+ }
247
+
248
+ if self.config.include_doc:
249
+ meta["title"] = spec.name
250
+ meta["application"] = f"Oscura reverse engineering (confidence: {spec.confidence:.2f})"
251
+ meta["file-extension"] = "bin"
252
+
253
+ return meta
254
+
255
+ def _generate_sequence(self, spec: ProtocolSpec) -> list[dict[str, Any]]:
256
+ """Generate seq section with field definitions.
257
+
258
+ Args:
259
+ spec: Protocol specification.
260
+
261
+ Returns:
262
+ List of field definitions.
263
+ """
264
+ seq: list[dict[str, Any]] = []
265
+
266
+ for field in spec.fields:
267
+ field_def = self._generate_field_definition(field, spec)
268
+ if field_def:
269
+ seq.append(field_def)
270
+
271
+ return seq
272
+
273
+ def _generate_field_definition(
274
+ self,
275
+ field: Any,
276
+ spec: ProtocolSpec,
277
+ ) -> dict[str, Any]:
278
+ """Generate Kaitai field definition for a single field.
279
+
280
+ Args:
281
+ field: FieldSpec from protocol specification.
282
+ spec: Complete protocol specification (for context).
283
+
284
+ Returns:
285
+ Kaitai field definition dictionary.
286
+ """
287
+ field_def: dict[str, Any] = {
288
+ "id": self._sanitize_field_name(field.name),
289
+ }
290
+
291
+ # Determine Kaitai type
292
+ self._set_field_type(field, field_def)
293
+
294
+ # Add enum if present
295
+ self._add_enum_reference(field, field_def)
296
+
297
+ # Add documentation
298
+ self._add_field_documentation(field, field_def)
299
+
300
+ return field_def
301
+
302
+ def _set_field_type(self, field: Any, field_def: dict[str, Any]) -> None:
303
+ """Set Kaitai type for field.
304
+
305
+ Args:
306
+ field: Field specification.
307
+ field_def: Field definition dictionary to update.
308
+ """
309
+ if field.field_type == "uint8":
310
+ field_def["type"] = "u1"
311
+ elif field.field_type == "uint16":
312
+ field_def["type"] = "u2"
313
+ elif field.field_type == "uint32":
314
+ field_def["type"] = "u4"
315
+ elif field.field_type == "string":
316
+ self._set_string_type(field, field_def)
317
+ elif field.field_type == "bytes":
318
+ self._set_bytes_type(field, field_def)
319
+ elif field.field_type == "constant":
320
+ self._set_constant_type(field, field_def)
321
+ elif field.field_type == "checksum":
322
+ self._set_checksum_type(field, field_def)
323
+
324
+ def _set_string_type(self, field: Any, field_def: dict[str, Any]) -> None:
325
+ """Set string field type."""
326
+ if isinstance(field.size, int):
327
+ field_def["type"] = "str"
328
+ field_def["size"] = field.size
329
+ field_def["encoding"] = "UTF-8"
330
+ else:
331
+ field_def["type"] = "bytes"
332
+ if isinstance(field.size, int):
333
+ field_def["size"] = field.size
334
+
335
+ def _set_bytes_type(self, field: Any, field_def: dict[str, Any]) -> None:
336
+ """Set bytes field type."""
337
+ field_def["type"] = "bytes"
338
+ if isinstance(field.size, int):
339
+ field_def["size"] = field.size
340
+
341
+ def _set_constant_type(self, field: Any, field_def: dict[str, Any]) -> None:
342
+ """Set constant field type with validation."""
343
+ field_def["type"] = "bytes"
344
+ if isinstance(field.size, int):
345
+ field_def["size"] = field.size
346
+ if hasattr(field, "value") and field.value:
347
+ if isinstance(field.value, str):
348
+ const_bytes = bytes.fromhex(field.value.replace("0x", ""))
349
+ field_def["contents"] = list(const_bytes)
350
+
351
+ def _set_checksum_type(self, field: Any, field_def: dict[str, Any]) -> None:
352
+ """Set checksum field type."""
353
+ field_def["type"] = "bytes"
354
+ if isinstance(field.size, int):
355
+ field_def["size"] = field.size
356
+ else:
357
+ field_def["size"] = 1
358
+
359
+ def _add_enum_reference(self, field: Any, field_def: dict[str, Any]) -> None:
360
+ """Add enum reference if field has enum."""
361
+ if hasattr(field, "enum") and field.enum:
362
+ enum_name = f"{self._sanitize_field_name(field.name)}_enum"
363
+ field_def["enum"] = enum_name
364
+
365
+ def _add_field_documentation(self, field: Any, field_def: dict[str, Any]) -> None:
366
+ """Add documentation if enabled."""
367
+ if self.config.include_doc:
368
+ doc_parts: list[str] = []
369
+ doc_parts.append(f"Field: {field.name}")
370
+ if field.field_type:
371
+ doc_parts.append(f"Type: {field.field_type}")
372
+ if hasattr(field, "value") and field.value:
373
+ doc_parts.append(f"Expected value: {field.value}")
374
+ field_def["doc"] = " | ".join(doc_parts)
375
+
376
+ def _generate_enums(self, spec: ProtocolSpec) -> dict[str, dict[int, str]]:
377
+ """Generate enums section for fields with enum values.
378
+
379
+ Args:
380
+ spec: Protocol specification.
381
+
382
+ Returns:
383
+ Dictionary mapping enum names to value mappings.
384
+ """
385
+ enums: dict[str, dict[int, str]] = {}
386
+
387
+ for field in spec.fields:
388
+ if hasattr(field, "enum") and field.enum:
389
+ enum_name = f"{self._sanitize_field_name(field.name)}_enum"
390
+ enums[enum_name] = field.enum
391
+
392
+ return enums
393
+
394
+ def _generate_documentation(self, spec: ProtocolSpec) -> str:
395
+ """Generate top-level documentation string.
396
+
397
+ Args:
398
+ spec: Protocol specification.
399
+
400
+ Returns:
401
+ Documentation string.
402
+ """
403
+ doc_lines: list[str] = [
404
+ f"Protocol: {spec.name}",
405
+ f"Reverse engineered with Oscura (confidence: {spec.confidence:.2f})",
406
+ "",
407
+ f"Baud Rate: {spec.baud_rate} bps",
408
+ f"Frame Format: {spec.frame_format}",
409
+ ]
410
+
411
+ if spec.sync_pattern:
412
+ doc_lines.append(f"Sync Pattern: 0x{spec.sync_pattern}")
413
+
414
+ if spec.frame_length:
415
+ doc_lines.append(f"Frame Length: {spec.frame_length} bytes")
416
+
417
+ if spec.checksum_type:
418
+ doc_lines.append(f"Checksum Type: {spec.checksum_type}")
419
+ if spec.checksum_position is not None:
420
+ pos_desc = (
421
+ "end of frame"
422
+ if spec.checksum_position == -1
423
+ else f"offset {spec.checksum_position}"
424
+ )
425
+ doc_lines.append(f"Checksum Position: {pos_desc}")
426
+
427
+ return "\n".join(doc_lines)
428
+
429
+ def _sanitize_field_name(self, name: str) -> str:
430
+ """Sanitize field name for Kaitai Struct compatibility.
431
+
432
+ Args:
433
+ name: Original field name.
434
+
435
+ Returns:
436
+ Sanitized field name (lowercase, underscores).
437
+ """
438
+ # Convert to lowercase, replace spaces and hyphens with underscores
439
+ sanitized = name.lower().replace(" ", "_").replace("-", "_")
440
+ # Remove any non-alphanumeric characters except underscores
441
+ sanitized = "".join(c if c.isalnum() or c == "_" else "" for c in sanitized)
442
+ # Ensure it doesn't start with a number
443
+ if sanitized and sanitized[0].isdigit():
444
+ sanitized = f"field_{sanitized}"
445
+ return sanitized
446
+
447
+ def _generate_yaml(self, ksy_data: dict[str, Any]) -> str:
448
+ """Convert .ksy structure to YAML string.
449
+
450
+ Args:
451
+ ksy_data: Complete .ksy structure dictionary.
452
+
453
+ Returns:
454
+ YAML string with proper formatting.
455
+ """
456
+ # Use default_flow_style=False for readable block style
457
+ # Use sort_keys=False to preserve order
458
+ yaml_str = yaml.dump(
459
+ ksy_data,
460
+ default_flow_style=False,
461
+ sort_keys=False,
462
+ allow_unicode=True,
463
+ width=100,
464
+ )
465
+ # yaml.dump returns str when given a dict
466
+ assert isinstance(yaml_str, str)
467
+ return yaml_str
468
+
469
+ def _validate_ksy_syntax(self, yaml_content: str) -> bool:
470
+ """Validate .ksy syntax using kaitai-struct-compiler if available.
471
+
472
+ Args:
473
+ yaml_content: YAML content to validate.
474
+
475
+ Returns:
476
+ True if syntax is valid or ksc not available, False if errors found.
477
+ """
478
+ try:
479
+ # First, verify it's valid YAML
480
+ yaml.safe_load(yaml_content)
481
+ except yaml.YAMLError as e:
482
+ logger.error(f"YAML syntax error: {e}")
483
+ return False
484
+
485
+ # Try to validate with kaitai-struct-compiler
486
+ try:
487
+ # Use ksc to validate the .ksy file
488
+ result = subprocess.run(
489
+ ["ksc", "--version"],
490
+ capture_output=True,
491
+ timeout=5,
492
+ check=False,
493
+ )
494
+ if result.returncode != 0:
495
+ # ksc not available
496
+ logger.warning("kaitai-struct-compiler not found, skipping .ksy validation")
497
+ return True
498
+
499
+ # ksc is available, we could validate here
500
+ # For now, just confirm YAML is valid (ksc validation would need temp file)
501
+ logger.info(".ksy YAML syntax validation passed")
502
+ return True
503
+
504
+ except FileNotFoundError:
505
+ # ksc not available, skip validation
506
+ logger.warning("kaitai-struct-compiler not found, skipping .ksy validation")
507
+ return True
508
+ except subprocess.TimeoutExpired:
509
+ logger.warning(".ksy syntax validation timed out")
510
+ return True
511
+ except Exception as e:
512
+ logger.warning(f".ksy syntax validation failed: {e}")
513
+ return True