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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +183 -67
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +1 -1
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -291
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
oscura/cli/completion.py
ADDED
|
@@ -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))
|
oscura/cli/config_cmd.py
ADDED
|
@@ -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
|