oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,250 @@
1
+ """Shell completion support for Oscura CLI.
2
+
3
+ Generates completion scripts for bash, zsh, and fish shells.
4
+
5
+
6
+ Example:
7
+ $ oscura --install-completion bash
8
+ $ oscura --show-completion bash > ~/.bash_completion.d/oscura
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+ from pathlib import Path
15
+
16
+
17
+ def get_completion_script(shell: str) -> str:
18
+ """Get completion script for specified shell.
19
+
20
+ Args:
21
+ shell: Shell type ('bash', 'zsh', or 'fish').
22
+
23
+ Returns:
24
+ Completion script content.
25
+
26
+ Raises:
27
+ ValueError: If shell type is unsupported.
28
+ """
29
+ if shell == "bash":
30
+ return _get_bash_completion()
31
+ elif shell == "zsh":
32
+ return _get_zsh_completion()
33
+ elif shell == "fish":
34
+ return _get_fish_completion()
35
+ else:
36
+ raise ValueError(f"Unsupported shell: {shell}")
37
+
38
+
39
+ def install_completion(shell: str) -> Path:
40
+ """Install completion script for specified shell.
41
+
42
+ Args:
43
+ shell: Shell type ('bash', 'zsh', or 'fish').
44
+
45
+ Returns:
46
+ Path where completion was installed.
47
+
48
+ Raises:
49
+ ValueError: If shell type is unsupported.
50
+ """
51
+ script = get_completion_script(shell)
52
+ home = Path.home()
53
+
54
+ if shell == "bash":
55
+ completion_dir = home / ".bash_completion.d"
56
+ completion_dir.mkdir(exist_ok=True)
57
+ completion_file = completion_dir / "oscura"
58
+ elif shell == "zsh":
59
+ completion_dir = home / ".zsh" / "completion"
60
+ completion_dir.mkdir(parents=True, exist_ok=True)
61
+ completion_file = completion_dir / "_oscura"
62
+ elif shell == "fish":
63
+ completion_dir = home / ".config" / "fish" / "completions"
64
+ completion_dir.mkdir(parents=True, exist_ok=True)
65
+ completion_file = completion_dir / "oscura.fish"
66
+ else:
67
+ raise ValueError(f"Unsupported shell: {shell}")
68
+
69
+ with open(completion_file, "w") as f:
70
+ f.write(script)
71
+
72
+ return completion_file
73
+
74
+
75
+ def _get_bash_completion() -> str:
76
+ """Get bash completion script."""
77
+ return """# Bash completion for oscura
78
+
79
+ _oscura_completion() {
80
+ local cur prev opts
81
+ COMPREPLY=()
82
+ cur="${COMP_WORDS[COMP_CWORD]}"
83
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
84
+
85
+ # Main commands
86
+ local commands="analyze decode export visualize benchmark validate config plugins characterize batch compare shell tutorial"
87
+
88
+ # Global options
89
+ local global_opts="--help --version --verbose --quiet --config --json"
90
+
91
+ # If we're on the first argument, complete commands or global options
92
+ if [[ ${COMP_CWORD} -eq 1 ]]; then
93
+ COMPREPLY=( $(compgen -W "${commands} ${global_opts}" -- ${cur}) )
94
+ return 0
95
+ fi
96
+
97
+ # Get the command
98
+ local cmd="${COMP_WORDS[1]}"
99
+
100
+ # Command-specific completions
101
+ case "${cmd}" in
102
+ analyze|decode|export|visualize)
103
+ # Complete file paths and help
104
+ if [[ ${cur} == -* ]]; then
105
+ COMPREPLY=( $(compgen -W "--help" -- ${cur}) )
106
+ else
107
+ COMPREPLY=( $(compgen -f -X '!*.@(wfm|vcd|csv|pcap|wav)' -- ${cur}) )
108
+ fi
109
+ ;;
110
+ config)
111
+ local config_opts="--show --set --edit --init --path --help"
112
+ COMPREPLY=( $(compgen -W "${config_opts}" -- ${cur}) )
113
+ ;;
114
+ plugins)
115
+ local plugin_cmds="list info install remove update"
116
+ if [[ ${COMP_CWORD} -eq 2 ]]; then
117
+ COMPREPLY=( $(compgen -W "${plugin_cmds}" -- ${cur}) )
118
+ fi
119
+ ;;
120
+ *)
121
+ # Default to --help for other commands
122
+ if [[ ${cur} == -* ]]; then
123
+ COMPREPLY=( $(compgen -W "--help" -- ${cur}) )
124
+ fi
125
+ ;;
126
+ esac
127
+ }
128
+
129
+ complete -F _oscura_completion oscura
130
+ """
131
+
132
+
133
+ def _get_zsh_completion() -> str:
134
+ """Get zsh completion script."""
135
+ return """#compdef oscura
136
+
137
+ _oscura() {
138
+ local -a commands
139
+ commands=(
140
+ 'analyze:Run full analysis workflow'
141
+ 'decode:Decode protocol data'
142
+ 'export:Export analysis results'
143
+ 'visualize:Launch interactive viewer'
144
+ 'benchmark:Run performance benchmarks'
145
+ 'validate:Validate protocol specification'
146
+ 'config:Manage configuration'
147
+ 'plugins:Manage plugins'
148
+ 'characterize:Characterize signal'
149
+ 'batch:Batch process files'
150
+ 'compare:Compare signals'
151
+ 'shell:Start interactive shell'
152
+ 'tutorial:Run interactive tutorial'
153
+ )
154
+
155
+ local -a file_args
156
+ file_args=(
157
+ '*:waveform file:_files -g "*.{wfm,vcd,csv,pcap,wav}"'
158
+ )
159
+
160
+ _arguments -C \\
161
+ '(-h --help)'{-h,--help}'[Show help message]' \\
162
+ '(-v --verbose)'{-v,--verbose}'[Increase verbosity]' \\
163
+ '--config[Configuration file]:config file:_files' \\
164
+ '(-q --quiet)'{-q,--quiet}'[Quiet mode]' \\
165
+ '--json[JSON output mode]' \\
166
+ '1: :->command' \\
167
+ '*:: :->args'
168
+
169
+ case $state in
170
+ command)
171
+ _describe -t commands 'oscura commands' commands
172
+ ;;
173
+ args)
174
+ case $words[1] in
175
+ analyze|decode|visualize)
176
+ _files -g "*.{wfm,vcd,csv,pcap,wav}"
177
+ ;;
178
+ config)
179
+ _arguments \\
180
+ '--show[Show configuration]' \\
181
+ '--set[Set value]:key=value:' \\
182
+ '--edit[Edit configuration]' \\
183
+ '--init[Initialize configuration]' \\
184
+ '--path[Show config path]'
185
+ ;;
186
+ esac
187
+ ;;
188
+ esac
189
+ }
190
+
191
+ _oscura
192
+ """
193
+
194
+
195
+ def _get_fish_completion() -> str:
196
+ """Get fish completion script."""
197
+ return """# Fish completion for oscura
198
+
199
+ # Main commands
200
+ complete -c oscura -n "__fish_use_subcommand" -a analyze -d "Run full analysis workflow"
201
+ complete -c oscura -n "__fish_use_subcommand" -a decode -d "Decode protocol data"
202
+ complete -c oscura -n "__fish_use_subcommand" -a export -d "Export analysis results"
203
+ complete -c oscura -n "__fish_use_subcommand" -a visualize -d "Launch interactive viewer"
204
+ complete -c oscura -n "__fish_use_subcommand" -a benchmark -d "Run performance benchmarks"
205
+ complete -c oscura -n "__fish_use_subcommand" -a validate -d "Validate protocol specification"
206
+ complete -c oscura -n "__fish_use_subcommand" -a config -d "Manage configuration"
207
+ complete -c oscura -n "__fish_use_subcommand" -a plugins -d "Manage plugins"
208
+ complete -c oscura -n "__fish_use_subcommand" -a characterize -d "Characterize signal"
209
+ complete -c oscura -n "__fish_use_subcommand" -a batch -d "Batch process files"
210
+ complete -c oscura -n "__fish_use_subcommand" -a compare -d "Compare signals"
211
+ complete -c oscura -n "__fish_use_subcommand" -a shell -d "Start interactive shell"
212
+ complete -c oscura -n "__fish_use_subcommand" -a tutorial -d "Run interactive tutorial"
213
+
214
+ # Global options
215
+ complete -c oscura -s h -l help -d "Show help message"
216
+ complete -c oscura -s v -l verbose -d "Increase verbosity"
217
+ complete -c oscura -s q -l quiet -d "Quiet mode"
218
+ complete -c oscura -l config -d "Configuration file" -r
219
+ complete -c oscura -l json -d "JSON output mode"
220
+
221
+ # analyze subcommand
222
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -l protocol -d "Protocol hint"
223
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -l export-dir -d "Export directory" -r
224
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -s i -l interactive -d "Interactive mode"
225
+ complete -c oscura -n "__fish_seen_subcommand_from analyze" -l output -d "Output format" -a "json csv html table"
226
+
227
+ # decode subcommand
228
+ complete -c oscura -n "__fish_seen_subcommand_from decode" -l protocol -d "Protocol type" -a "uart spi i2c can auto"
229
+ complete -c oscura -n "__fish_seen_subcommand_from decode" -l baud-rate -d "Baud rate" -r
230
+ complete -c oscura -n "__fish_seen_subcommand_from decode" -l show-errors -d "Show only errors"
231
+
232
+ # config subcommand
233
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l show -d "Show configuration"
234
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l set -d "Set value" -r
235
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l edit -d "Edit configuration"
236
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l init -d "Initialize configuration"
237
+ complete -c oscura -n "__fish_seen_subcommand_from config" -l path -d "Show config path"
238
+
239
+ # File completions for commands that take file arguments
240
+ for cmd in analyze decode visualize export
241
+ complete -c oscura -n "__fish_seen_subcommand_from $cmd" -F -a '(__fish_complete_suffix .wfm .vcd .csv .pcap .wav)'
242
+ end
243
+ """
244
+
245
+
246
+ if __name__ == "__main__":
247
+ # Allow running as: python -m oscura.cli.completion bash
248
+ if len(sys.argv) > 1:
249
+ shell_type = sys.argv[1]
250
+ print(get_completion_script(shell_type))
@@ -0,0 +1,361 @@
1
+ """Oscura Config Command - Configuration Management.
2
+
3
+ Provides CLI for viewing and editing Oscura configuration.
4
+
5
+
6
+ Example:
7
+ $ oscura config --show
8
+ $ oscura config --set analysis.default_protocol=uart
9
+ $ oscura config --edit
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import os
16
+ import shlex
17
+ import subprocess
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ import click
22
+
23
+ logger = logging.getLogger("oscura.cli.config")
24
+
25
+ # Allowlist of trusted editors (SEC-004 fix)
26
+ ALLOWED_EDITORS = {
27
+ "nano",
28
+ "vim",
29
+ "vi",
30
+ "emacs",
31
+ "nvim",
32
+ "code",
33
+ "subl",
34
+ "atom",
35
+ "gedit",
36
+ "kate",
37
+ "micro",
38
+ "helix",
39
+ }
40
+
41
+
42
+ def _get_safe_editor() -> str:
43
+ """Get validated editor from environment.
44
+
45
+ Returns:
46
+ Safe editor command.
47
+
48
+ Security:
49
+ SEC-004 fix: Validates editor against allowlist to prevent command injection
50
+ via $EDITOR environment variable. Falls back to 'nano' for untrusted editors.
51
+
52
+ Example:
53
+ >>> os.environ["EDITOR"] = "vim"
54
+ >>> editor = _get_safe_editor()
55
+ >>> assert editor == "vim"
56
+
57
+ >>> os.environ["EDITOR"] = "rm -rf /"
58
+ >>> editor = _get_safe_editor()
59
+ >>> assert editor == "nano" # Fallback to safe default
60
+
61
+ References:
62
+ https://owasp.org/www-project-top-ten/
63
+ """
64
+ editor_env = os.environ.get("EDITOR", "nano")
65
+
66
+ # Check for command substitution and newlines before parsing
67
+ # These are shell injection attempts that shlex may not detect
68
+ if "`" in editor_env or "$(" in editor_env or "\n" in editor_env or "\r" in editor_env:
69
+ logger.warning(
70
+ "Command substitution or newline detected in EDITOR, falling back to nano for safety"
71
+ )
72
+ return "nano"
73
+
74
+ # Extract base command (handle args like "code --wait")
75
+ try:
76
+ editor_parts = shlex.split(editor_env)
77
+ if not editor_parts:
78
+ logger.warning("Empty EDITOR value, using nano")
79
+ return "nano"
80
+
81
+ editor_cmd = Path(editor_parts[0]).name
82
+ except ValueError as e:
83
+ logger.warning(f"Invalid EDITOR value '{editor_env}': {e}, using nano")
84
+ return "nano"
85
+
86
+ # Validate against allowlist
87
+ if editor_cmd not in ALLOWED_EDITORS:
88
+ logger.warning(
89
+ f"Untrusted editor '{editor_cmd}' not in allowlist, falling back to nano. "
90
+ f"Allowed editors: {', '.join(sorted(ALLOWED_EDITORS))}"
91
+ )
92
+ return "nano"
93
+
94
+ # Check for shell metacharacters that indicate command injection attempts
95
+ # Shell metacharacters parsed as separate tokens by shlex indicate injection
96
+ shell_metacharacters = {"&&", "||", ";", "|", ">", "<", ">>", "<<", "&"}
97
+ if len(editor_parts) > 1 and any(part in shell_metacharacters for part in editor_parts[1:]):
98
+ logger.warning(
99
+ f"Shell metacharacters detected in EDITOR '{editor_env}', "
100
+ f"falling back to nano for safety"
101
+ )
102
+ return "nano"
103
+
104
+ return editor_env # Return full command with args if valid
105
+
106
+
107
+ @click.command()
108
+ @click.option(
109
+ "--show",
110
+ is_flag=True,
111
+ help="Show current configuration.",
112
+ )
113
+ @click.option(
114
+ "--set",
115
+ "set_value",
116
+ type=str,
117
+ default=None,
118
+ help="Set configuration value (key=value).",
119
+ )
120
+ @click.option(
121
+ "--edit",
122
+ is_flag=True,
123
+ help="Open configuration file in editor.",
124
+ )
125
+ @click.option(
126
+ "--init",
127
+ is_flag=True,
128
+ help="Initialize default configuration file.",
129
+ )
130
+ @click.option(
131
+ "--path",
132
+ is_flag=True,
133
+ help="Show configuration file path.",
134
+ )
135
+ @click.pass_context
136
+ def config(
137
+ ctx: click.Context,
138
+ show: bool,
139
+ set_value: str | None,
140
+ edit: bool,
141
+ init: bool,
142
+ path: bool,
143
+ ) -> None:
144
+ """Manage Oscura configuration.
145
+
146
+ View, edit, and initialize configuration files.
147
+
148
+ Args:
149
+ ctx: Click context object.
150
+ show: Show configuration.
151
+ set_value: Set configuration value.
152
+ edit: Edit configuration file.
153
+ init: Initialize configuration.
154
+ path: Show config path.
155
+
156
+ Examples:
157
+
158
+ \b
159
+ # Show current config
160
+ $ oscura config --show
161
+
162
+ \b
163
+ # Set a value
164
+ $ oscura config --set analysis.default_protocol=uart
165
+
166
+ \b
167
+ # Edit config file
168
+ $ oscura config --edit
169
+
170
+ \b
171
+ # Initialize config
172
+ $ oscura config --init
173
+ """
174
+ verbose = ctx.obj.get("verbose", 0)
175
+
176
+ try:
177
+ config_path = _get_config_path()
178
+
179
+ if path:
180
+ click.echo(f"Configuration file: {config_path}")
181
+ return
182
+
183
+ if init:
184
+ _initialize_config(config_path)
185
+ click.echo(f"Initialized configuration at: {config_path}")
186
+ return
187
+
188
+ if show:
189
+ _show_config(config_path)
190
+ return
191
+
192
+ if set_value:
193
+ _set_config_value(config_path, set_value)
194
+ click.echo(f"Updated configuration: {set_value}")
195
+ return
196
+
197
+ if edit:
198
+ _edit_config(config_path)
199
+ return
200
+
201
+ # No options provided, show help
202
+ click.echo(ctx.get_help())
203
+
204
+ except Exception as e:
205
+ logger.error(f"Config operation failed: {e}")
206
+ if verbose > 1:
207
+ raise
208
+ click.echo(f"Error: {e}", err=True)
209
+ ctx.exit(1)
210
+
211
+
212
+ def _get_config_path() -> Path:
213
+ """Get configuration file path.
214
+
215
+ Returns:
216
+ Path to configuration file (absolute path).
217
+ """
218
+ # Check for local config first
219
+ local_config = Path(".oscura.yaml").resolve()
220
+ if local_config.exists():
221
+ return local_config
222
+
223
+ # Use user config
224
+ user_config = Path.home() / ".config" / "oscura" / "config.yaml"
225
+ return user_config
226
+
227
+
228
+ def _initialize_config(config_path: Path) -> None:
229
+ """Initialize default configuration file.
230
+
231
+ Args:
232
+ config_path: Path to configuration file.
233
+ """
234
+ config_path.parent.mkdir(parents=True, exist_ok=True)
235
+
236
+ default_config = """# Oscura Configuration
237
+
238
+ analysis:
239
+ default_protocol: auto
240
+ auto_detect_threshold: 0.7
241
+ max_packets: 10000
242
+
243
+ export:
244
+ default_format: json
245
+ output_dir: oscura_output
246
+
247
+ visualization:
248
+ default_backend: matplotlib
249
+ figure_size: [12, 6]
250
+ dpi: 100
251
+
252
+ cli:
253
+ color_output: true
254
+ progress_bars: true
255
+
256
+ logging:
257
+ level: WARNING
258
+ format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
259
+ """
260
+
261
+ with open(config_path, "w") as f:
262
+ f.write(default_config)
263
+
264
+
265
+ def _show_config(config_path: Path) -> None:
266
+ """Show configuration.
267
+
268
+ Args:
269
+ config_path: Path to configuration file.
270
+ """
271
+ if not config_path.exists():
272
+ click.echo("No configuration file found. Use --init to create one.")
273
+ return
274
+
275
+ import yaml
276
+
277
+ with open(config_path) as f:
278
+ config = yaml.safe_load(f)
279
+
280
+ click.echo(f"\nConfiguration ({config_path}):\n")
281
+ click.echo(yaml.dump(config, default_flow_style=False))
282
+
283
+
284
+ def _set_config_value(config_path: Path, set_value: str) -> None:
285
+ """Set configuration value.
286
+
287
+ Args:
288
+ config_path: Path to configuration file.
289
+ set_value: Value to set (key=value format).
290
+ """
291
+ import yaml
292
+
293
+ if "=" not in set_value:
294
+ raise ValueError("Invalid format. Use: key=value")
295
+
296
+ key, value = set_value.split("=", 1)
297
+ keys = key.split(".")
298
+
299
+ # Load existing config
300
+ config: dict[str, Any] = {}
301
+ if config_path.exists():
302
+ with open(config_path) as f:
303
+ config = yaml.safe_load(f) or {}
304
+
305
+ # Set nested value
306
+ current = config
307
+ for k in keys[:-1]:
308
+ if k not in current:
309
+ current[k] = {}
310
+ current = current[k]
311
+
312
+ # Try to parse value
313
+ try:
314
+ # Try as number
315
+ if "." in value:
316
+ parsed_value: Any = float(value)
317
+ else:
318
+ parsed_value = int(value)
319
+ except ValueError:
320
+ # Try as boolean
321
+ if value.lower() in ["true", "false"]:
322
+ parsed_value = value.lower() == "true"
323
+ else:
324
+ # Keep as string
325
+ parsed_value = value
326
+
327
+ current[keys[-1]] = parsed_value
328
+
329
+ # Save config
330
+ config_path.parent.mkdir(parents=True, exist_ok=True)
331
+ with open(config_path, "w") as f:
332
+ yaml.dump(config, f, default_flow_style=False)
333
+
334
+
335
+ def _edit_config(config_path: Path) -> None:
336
+ """Edit configuration file with safe editor validation.
337
+
338
+ Args:
339
+ config_path: Path to configuration file.
340
+
341
+ Security:
342
+ SEC-004 fix: Uses _get_safe_editor() to validate $EDITOR against allowlist,
343
+ preventing command injection attacks via malicious editor values.
344
+
345
+ Raises:
346
+ RuntimeError: If editor execution fails.
347
+ """
348
+ # Create if doesn't exist
349
+ if not config_path.exists():
350
+ _initialize_config(config_path)
351
+
352
+ # Get validated editor (SEC-004 fix)
353
+ editor_cmd = _get_safe_editor()
354
+
355
+ # Open editor with validated command
356
+ try:
357
+ # Parse editor command (may include args like "code --wait")
358
+ editor_parts = shlex.split(editor_cmd)
359
+ subprocess.run([*editor_parts, str(config_path)], check=True)
360
+ except (subprocess.CalledProcessError, OSError, ValueError) as e:
361
+ raise RuntimeError(f"Editor failed: {e}") from e