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
|
@@ -94,98 +94,122 @@ class DataQuality:
|
|
|
94
94
|
improvement_suggestions: list[dict[str, str]] = field(default_factory=list)
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
def
|
|
97
|
+
def _extract_trace_data(
|
|
98
98
|
trace: WaveformTrace | DigitalTrace,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
protocol_params: dict[str, Any] | None = None,
|
|
102
|
-
strict_mode: bool = False,
|
|
103
|
-
) -> DataQuality:
|
|
104
|
-
"""Assess whether captured data is adequate for analysis.
|
|
105
|
-
|
|
106
|
-
Evaluates sample rate, resolution, duration, and noise level against
|
|
107
|
-
scenario-specific requirements.
|
|
99
|
+
) -> tuple[NDArray[np.floating[Any]], float, bool]:
|
|
100
|
+
"""Extract data array, sample rate, and type from trace.
|
|
108
101
|
|
|
109
102
|
Args:
|
|
110
|
-
trace: Input
|
|
111
|
-
scenario: Analysis scenario for scenario-specific thresholds.
|
|
112
|
-
protocol_params: Protocol-specific parameters (e.g., clock frequency).
|
|
113
|
-
strict_mode: If True, fail on any warnings.
|
|
103
|
+
trace: Input trace.
|
|
114
104
|
|
|
115
105
|
Returns:
|
|
116
|
-
|
|
106
|
+
Tuple of (data, sample_rate, is_analog).
|
|
117
107
|
|
|
118
108
|
Raises:
|
|
119
|
-
ValueError: If trace is empty
|
|
120
|
-
|
|
121
|
-
Example:
|
|
122
|
-
>>> quality = assess_data_quality(trace, scenario='protocol_decode')
|
|
123
|
-
>>> print(f"Overall: {quality.status} (confidence: {quality.confidence:.2f})")
|
|
124
|
-
>>> for metric in quality.metrics:
|
|
125
|
-
... if metric.status != 'PASS':
|
|
126
|
-
... print(f"Issue: {metric.name} - {metric.explanation}")
|
|
127
|
-
... print(f"Fix: {metric.recommendation}")
|
|
128
|
-
|
|
129
|
-
References:
|
|
130
|
-
DISC-009: Data Quality Assessment
|
|
109
|
+
ValueError: If trace is empty.
|
|
131
110
|
"""
|
|
132
|
-
# Validate input
|
|
133
111
|
if len(trace) == 0:
|
|
134
112
|
raise ValueError("Cannot assess quality of empty trace")
|
|
135
113
|
|
|
136
|
-
# Get signal data
|
|
137
114
|
if isinstance(trace, WaveformTrace):
|
|
138
|
-
data
|
|
139
|
-
sample_rate = trace.metadata.sample_rate
|
|
140
|
-
is_analog = True
|
|
115
|
+
return trace.data, trace.metadata.sample_rate, True
|
|
141
116
|
else:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
is_analog = False
|
|
117
|
+
return trace.data.astype(np.float64), trace.metadata.sample_rate, False
|
|
118
|
+
|
|
145
119
|
|
|
146
|
-
|
|
120
|
+
def _prepare_quality_assessment(
|
|
121
|
+
data: NDArray[np.floating[Any]], protocol_params: dict[str, Any] | None
|
|
122
|
+
) -> tuple[dict[str, float], float, dict[str, Any]]:
|
|
123
|
+
"""Prepare data for quality assessment.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
data: Signal data array.
|
|
127
|
+
protocol_params: Optional protocol parameters.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Tuple of (stats, voltage_swing, protocol_params).
|
|
131
|
+
"""
|
|
147
132
|
stats = basic_stats(data)
|
|
148
133
|
voltage_swing = stats["max"] - stats["min"]
|
|
134
|
+
return stats, voltage_swing, protocol_params or {}
|
|
135
|
+
|
|
149
136
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
137
|
+
def _assess_all_quality_metrics(
|
|
138
|
+
data: NDArray[np.floating[Any]],
|
|
139
|
+
sample_rate: float,
|
|
140
|
+
voltage_swing: float,
|
|
141
|
+
stats: dict[str, float],
|
|
142
|
+
is_analog: bool,
|
|
143
|
+
scenario: AnalysisScenario,
|
|
144
|
+
protocol_params: dict[str, Any],
|
|
145
|
+
) -> list[QualityMetric]:
|
|
146
|
+
"""Assess all quality metrics.
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
Args:
|
|
149
|
+
data: Signal data array.
|
|
150
|
+
sample_rate: Sample rate in Hz.
|
|
151
|
+
voltage_swing: Peak-to-peak voltage.
|
|
152
|
+
stats: Basic statistics.
|
|
153
|
+
is_analog: Whether signal is analog.
|
|
154
|
+
scenario: Analysis scenario.
|
|
155
|
+
protocol_params: Protocol parameters.
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
Returns:
|
|
158
|
+
List of quality metrics.
|
|
159
|
+
"""
|
|
160
|
+
return [
|
|
161
|
+
_assess_sample_rate(sample_rate, data, stats, scenario, protocol_params),
|
|
162
|
+
_assess_resolution(data, voltage_swing, stats, is_analog, scenario),
|
|
163
|
+
_assess_duration(len(data), sample_rate, data, scenario, protocol_params),
|
|
164
|
+
_assess_noise(data, voltage_swing, stats, scenario),
|
|
165
|
+
]
|
|
160
166
|
|
|
161
|
-
# 2. Resolution Assessment
|
|
162
|
-
resolution_metric = _assess_resolution(data, voltage_swing, stats, is_analog, scenario)
|
|
163
|
-
metrics.append(resolution_metric)
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
def _determine_overall_quality_status(
|
|
169
|
+
metrics: list[QualityMetric], strict_mode: bool
|
|
170
|
+
) -> QualityStatus:
|
|
171
|
+
"""Determine overall quality status from individual metrics.
|
|
168
172
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
Args:
|
|
174
|
+
metrics: List of quality metrics.
|
|
175
|
+
strict_mode: Whether to fail on warnings.
|
|
172
176
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
177
|
+
Returns:
|
|
178
|
+
Overall quality status.
|
|
179
|
+
"""
|
|
180
|
+
failed = [m for m in metrics if m.status == "FAIL"]
|
|
181
|
+
warnings = [m for m in metrics if m.status == "WARNING"]
|
|
176
182
|
|
|
177
|
-
if
|
|
178
|
-
|
|
179
|
-
elif
|
|
180
|
-
|
|
183
|
+
if failed or (strict_mode and warnings):
|
|
184
|
+
return "FAIL"
|
|
185
|
+
elif warnings:
|
|
186
|
+
return "WARNING"
|
|
181
187
|
else:
|
|
182
|
-
|
|
188
|
+
return "PASS"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _calculate_quality_confidence(metrics: list[QualityMetric]) -> float:
|
|
192
|
+
"""Calculate quality confidence score.
|
|
183
193
|
|
|
184
|
-
|
|
194
|
+
Args:
|
|
195
|
+
metrics: List of quality metrics.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Confidence score (0.5-1.0).
|
|
199
|
+
"""
|
|
185
200
|
passed_count = sum(1 for m in metrics if m.passed)
|
|
186
|
-
|
|
201
|
+
return round(0.5 + (passed_count / len(metrics)) * 0.5, 2)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _generate_improvement_suggestions(metrics: list[QualityMetric]) -> list[dict[str, str]]:
|
|
205
|
+
"""Generate improvement suggestions from failed metrics.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
metrics: List of quality metrics.
|
|
187
209
|
|
|
188
|
-
|
|
210
|
+
Returns:
|
|
211
|
+
List of suggestion dictionaries.
|
|
212
|
+
"""
|
|
189
213
|
suggestions = []
|
|
190
214
|
for metric in metrics:
|
|
191
215
|
if not metric.passed and metric.recommendation:
|
|
@@ -198,6 +222,57 @@ def assess_data_quality(
|
|
|
198
222
|
else "Medium",
|
|
199
223
|
}
|
|
200
224
|
)
|
|
225
|
+
return suggestions
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def assess_data_quality(
|
|
229
|
+
trace: WaveformTrace | DigitalTrace,
|
|
230
|
+
*,
|
|
231
|
+
scenario: AnalysisScenario = "general",
|
|
232
|
+
protocol_params: dict[str, Any] | None = None,
|
|
233
|
+
strict_mode: bool = False,
|
|
234
|
+
) -> DataQuality:
|
|
235
|
+
"""Assess whether captured data is adequate for analysis.
|
|
236
|
+
|
|
237
|
+
Evaluates sample rate, resolution, duration, and noise level against
|
|
238
|
+
scenario-specific requirements.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
trace: Input waveform or digital trace.
|
|
242
|
+
scenario: Analysis scenario for scenario-specific thresholds.
|
|
243
|
+
protocol_params: Protocol-specific parameters (e.g., clock frequency).
|
|
244
|
+
strict_mode: If True, fail on any warnings.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
DataQuality assessment with overall status and individual metrics.
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
ValueError: If trace is empty or invalid.
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> quality = assess_data_quality(trace, scenario='protocol_decode')
|
|
254
|
+
>>> print(f"Overall: {quality.status} (confidence: {quality.confidence:.2f})")
|
|
255
|
+
>>> for metric in quality.metrics:
|
|
256
|
+
... if metric.status != 'PASS':
|
|
257
|
+
... print(f"Issue: {metric.name} - {metric.explanation}")
|
|
258
|
+
... print(f"Fix: {metric.recommendation}")
|
|
259
|
+
|
|
260
|
+
References:
|
|
261
|
+
DISC-009: Data Quality Assessment
|
|
262
|
+
"""
|
|
263
|
+
# Setup: extract and prepare signal data
|
|
264
|
+
data, sample_rate, is_analog = _extract_trace_data(trace)
|
|
265
|
+
stats, voltage_swing, protocol_params = _prepare_quality_assessment(data, protocol_params)
|
|
266
|
+
|
|
267
|
+
# Processing: assess individual metrics
|
|
268
|
+
metrics = _assess_all_quality_metrics(
|
|
269
|
+
data, sample_rate, voltage_swing, stats, is_analog, scenario, protocol_params
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Formatting: determine overall status and generate report
|
|
273
|
+
overall_status = _determine_overall_quality_status(metrics, strict_mode)
|
|
274
|
+
confidence = _calculate_quality_confidence(metrics)
|
|
275
|
+
suggestions = _generate_improvement_suggestions(metrics)
|
|
201
276
|
|
|
202
277
|
return DataQuality(
|
|
203
278
|
status=overall_status,
|
|
@@ -272,7 +347,10 @@ def _assess_sample_rate(
|
|
|
272
347
|
status = "WARNING"
|
|
273
348
|
passed = False
|
|
274
349
|
explanation = f"Sample rate is {abs(margin_percent):.0f}% below recommended"
|
|
275
|
-
recommendation =
|
|
350
|
+
recommendation = (
|
|
351
|
+
f"Increase sample rate to {required_rate / 1e6:.0f} MS/s "
|
|
352
|
+
f"(currently {sample_rate / 1e6:.0f} MS/s)"
|
|
353
|
+
)
|
|
276
354
|
else:
|
|
277
355
|
status = "FAIL"
|
|
278
356
|
passed = False
|
|
@@ -423,8 +501,14 @@ def _assess_duration(
|
|
|
423
501
|
elif num_periods >= required_periods * 0.5:
|
|
424
502
|
status = "WARNING"
|
|
425
503
|
passed = False
|
|
426
|
-
explanation =
|
|
427
|
-
|
|
504
|
+
explanation = (
|
|
505
|
+
f"Captured only {num_periods:.0f} signal periods, "
|
|
506
|
+
f"recommended minimum is {required_periods}"
|
|
507
|
+
)
|
|
508
|
+
recommendation = (
|
|
509
|
+
f"Increase capture duration to at least {required_duration * 1e3:.1f} ms "
|
|
510
|
+
f"(currently {duration_sec * 1e3:.1f} ms)"
|
|
511
|
+
)
|
|
428
512
|
else:
|
|
429
513
|
status = "FAIL"
|
|
430
514
|
passed = False
|
|
@@ -496,7 +580,10 @@ def _assess_noise(
|
|
|
496
580
|
elif noise_percent <= max_noise_percent * 1.5:
|
|
497
581
|
status = "WARNING"
|
|
498
582
|
passed = False
|
|
499
|
-
explanation =
|
|
583
|
+
explanation = (
|
|
584
|
+
f"Noise level is {noise_percent:.1f}% of signal swing "
|
|
585
|
+
f"(max recommended: {max_noise_percent:.0f}%)"
|
|
586
|
+
)
|
|
500
587
|
recommendation = "Reduce noise sources, check grounding, or use averaging"
|
|
501
588
|
else:
|
|
502
589
|
status = "FAIL"
|
|
@@ -98,121 +98,238 @@ def characterize_signal(
|
|
|
98
98
|
References:
|
|
99
99
|
DISC-001: Automatic Signal Characterization
|
|
100
100
|
"""
|
|
101
|
-
# Validate
|
|
101
|
+
# Validate and extract trace data
|
|
102
|
+
data, sample_rate, is_analog = _validate_and_extract_trace(trace)
|
|
103
|
+
|
|
104
|
+
# Compute basic statistics and voltage levels
|
|
105
|
+
stats = basic_stats(data)
|
|
106
|
+
voltage_low, voltage_high, voltage_swing = _compute_voltage_levels(data)
|
|
107
|
+
|
|
108
|
+
# Run all signal type detectors
|
|
109
|
+
candidates = _detect_all_signal_types(data, sample_rate, voltage_swing, is_analog)
|
|
110
|
+
|
|
111
|
+
# Select best signal type with refinement logic
|
|
112
|
+
best_type, best_confidence = _select_best_signal_type(candidates)
|
|
113
|
+
|
|
114
|
+
# Estimate dominant frequency
|
|
115
|
+
frequency_hz = _estimate_frequency(data, sample_rate)
|
|
116
|
+
|
|
117
|
+
# Extract type-specific parameters and quality metrics
|
|
118
|
+
parameters = _extract_parameters(best_type, data, sample_rate, voltage_low, voltage_high)
|
|
119
|
+
quality_metrics = _compute_quality_metrics(
|
|
120
|
+
data, stats, sample_rate, voltage_low, voltage_high, candidates["digital"]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Prepare alternatives if requested
|
|
124
|
+
alternatives = _build_alternatives(
|
|
125
|
+
candidates,
|
|
126
|
+
best_type,
|
|
127
|
+
best_confidence,
|
|
128
|
+
include_alternatives,
|
|
129
|
+
confidence_threshold,
|
|
130
|
+
min_alternatives,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return SignalCharacterization(
|
|
134
|
+
signal_type=best_type,
|
|
135
|
+
confidence=round(best_confidence, 2),
|
|
136
|
+
voltage_low=voltage_low,
|
|
137
|
+
voltage_high=voltage_high,
|
|
138
|
+
frequency_hz=frequency_hz,
|
|
139
|
+
parameters=parameters,
|
|
140
|
+
quality_metrics=quality_metrics,
|
|
141
|
+
alternatives=alternatives,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _validate_and_extract_trace(
|
|
146
|
+
trace: WaveformTrace | DigitalTrace,
|
|
147
|
+
) -> tuple[NDArray[np.floating[Any]], float, bool]:
|
|
148
|
+
"""Validate trace and extract data, sample rate, and analog flag.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
trace: Input waveform or digital trace.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Tuple of (data, sample_rate, is_analog).
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ValueError: If trace is empty.
|
|
158
|
+
"""
|
|
102
159
|
if len(trace) == 0:
|
|
103
160
|
raise ValueError("Cannot characterize empty trace")
|
|
104
161
|
|
|
105
|
-
# Get signal data
|
|
106
162
|
if isinstance(trace, WaveformTrace):
|
|
107
|
-
data
|
|
108
|
-
sample_rate = trace.metadata.sample_rate
|
|
109
|
-
is_analog = True
|
|
163
|
+
return trace.data, trace.metadata.sample_rate, True
|
|
110
164
|
else:
|
|
111
|
-
|
|
112
|
-
sample_rate = trace.metadata.sample_rate
|
|
113
|
-
is_analog = False
|
|
165
|
+
return trace.data.astype(np.float64), trace.metadata.sample_rate, False
|
|
114
166
|
|
|
115
|
-
# Compute basic statistics
|
|
116
|
-
stats = basic_stats(data)
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
168
|
+
def _compute_voltage_levels(
|
|
169
|
+
data: NDArray[np.floating[Any]],
|
|
170
|
+
) -> tuple[float, float, float]:
|
|
171
|
+
"""Compute voltage levels using robust percentiles.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
data: Signal data array.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Tuple of (voltage_low, voltage_high, voltage_swing).
|
|
178
|
+
"""
|
|
120
179
|
voltage_low = float(np.percentile(data, 5))
|
|
121
180
|
voltage_high = float(np.percentile(data, 95))
|
|
122
181
|
voltage_swing = voltage_high - voltage_low
|
|
182
|
+
return voltage_low, voltage_high, voltage_swing
|
|
123
183
|
|
|
124
|
-
# Analyze signal characteristics
|
|
125
|
-
candidates: dict[SignalType, float] = {}
|
|
126
184
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
185
|
+
def _detect_all_signal_types(
|
|
186
|
+
data: NDArray[np.floating[Any]],
|
|
187
|
+
sample_rate: float,
|
|
188
|
+
voltage_swing: float,
|
|
189
|
+
is_analog: bool,
|
|
190
|
+
) -> dict[SignalType, float]:
|
|
191
|
+
"""Run all signal type detectors and return confidence scores.
|
|
130
192
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
193
|
+
Args:
|
|
194
|
+
data: Signal data array.
|
|
195
|
+
sample_rate: Sample rate in Hz.
|
|
196
|
+
voltage_swing: Peak-to-peak voltage swing.
|
|
197
|
+
is_analog: Whether input is from analog trace.
|
|
134
198
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
199
|
+
Returns:
|
|
200
|
+
Dictionary mapping signal types to confidence scores.
|
|
201
|
+
"""
|
|
202
|
+
return {
|
|
203
|
+
"digital": _detect_digital(data, voltage_swing),
|
|
204
|
+
"analog": _detect_analog(data, voltage_swing, is_analog),
|
|
205
|
+
"pwm": _detect_pwm(data, sample_rate, voltage_swing),
|
|
206
|
+
"uart": _detect_uart(data, sample_rate, voltage_swing),
|
|
207
|
+
"spi": _detect_spi(data, sample_rate, voltage_swing),
|
|
208
|
+
"i2c": _detect_i2c(data, sample_rate, voltage_swing),
|
|
209
|
+
}
|
|
138
210
|
|
|
139
|
-
# Check for UART (asynchronous serial with start/stop bits)
|
|
140
|
-
uart_confidence = _detect_uart(data, sample_rate, voltage_swing)
|
|
141
|
-
candidates["uart"] = uart_confidence
|
|
142
211
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
212
|
+
def _select_best_signal_type(
|
|
213
|
+
candidates: dict[SignalType, float],
|
|
214
|
+
) -> tuple[SignalType, float]:
|
|
215
|
+
"""Select best signal type with refinement logic.
|
|
146
216
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
candidates["i2c"] = i2c_confidence
|
|
217
|
+
Args:
|
|
218
|
+
candidates: Dictionary of signal types and confidence scores.
|
|
150
219
|
|
|
151
|
-
|
|
220
|
+
Returns:
|
|
221
|
+
Tuple of (best_type, best_confidence).
|
|
222
|
+
"""
|
|
152
223
|
sorted_candidates = sorted(candidates.items(), key=lambda x: x[1], reverse=True)
|
|
153
224
|
best_type, best_confidence = sorted_candidates[0]
|
|
154
225
|
|
|
155
226
|
# If confidence is too low, mark as unknown
|
|
156
227
|
if best_confidence < 0.5:
|
|
157
|
-
|
|
228
|
+
return "unknown", best_confidence
|
|
158
229
|
|
|
159
|
-
#
|
|
160
|
-
# This handles noisy digital signals that look analog-ish
|
|
230
|
+
# Refine analog classification if digital characteristics present
|
|
161
231
|
if best_type == "analog":
|
|
162
|
-
|
|
163
|
-
protocol_confidence = max(
|
|
164
|
-
candidates.get("uart", 0),
|
|
165
|
-
candidates.get("spi", 0),
|
|
166
|
-
candidates.get("pwm", 0),
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
# If digital or protocol detectors have some confidence, don't call it purely analog
|
|
170
|
-
if digital_confidence > 0.3 or protocol_confidence > 0.2:
|
|
171
|
-
# Signal has digital characteristics - don't call it analog
|
|
172
|
-
if protocol_confidence > 0.3:
|
|
173
|
-
best_type = "unknown" # Too noisy/ambiguous to classify as specific protocol
|
|
174
|
-
best_confidence = protocol_confidence
|
|
175
|
-
elif digital_confidence > 0.4:
|
|
176
|
-
best_type = "digital" # Generic digital signal
|
|
177
|
-
best_confidence = digital_confidence
|
|
178
|
-
else:
|
|
179
|
-
best_type = "unknown" # Too ambiguous
|
|
180
|
-
best_confidence = max(digital_confidence, protocol_confidence, analog_confidence)
|
|
232
|
+
return _refine_analog_classification(candidates)
|
|
181
233
|
|
|
182
|
-
|
|
183
|
-
frequency_hz = _estimate_frequency(data, sample_rate)
|
|
234
|
+
return best_type, best_confidence
|
|
184
235
|
|
|
185
|
-
# Extract type-specific parameters
|
|
186
|
-
parameters = _extract_parameters(best_type, data, sample_rate, voltage_low, voltage_high)
|
|
187
236
|
|
|
188
|
-
|
|
237
|
+
def _refine_analog_classification(
|
|
238
|
+
candidates: dict[SignalType, float],
|
|
239
|
+
) -> tuple[SignalType, float]:
|
|
240
|
+
"""Refine analog classification when digital characteristics are present.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
candidates: Dictionary of signal types and confidence scores.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Tuple of (refined_type, confidence).
|
|
247
|
+
"""
|
|
248
|
+
digital_confidence = candidates.get("digital", 0)
|
|
249
|
+
analog_confidence = candidates.get("analog", 0)
|
|
250
|
+
protocol_confidence = max(
|
|
251
|
+
candidates.get("uart", 0),
|
|
252
|
+
candidates.get("spi", 0),
|
|
253
|
+
candidates.get("pwm", 0),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# If digital or protocol detectors have some confidence, don't call it purely analog
|
|
257
|
+
if digital_confidence > 0.3 or protocol_confidence > 0.2:
|
|
258
|
+
if protocol_confidence > 0.3:
|
|
259
|
+
return "unknown", protocol_confidence
|
|
260
|
+
elif digital_confidence > 0.4:
|
|
261
|
+
return "digital", digital_confidence
|
|
262
|
+
else:
|
|
263
|
+
return "unknown", max(digital_confidence, protocol_confidence, analog_confidence)
|
|
264
|
+
|
|
265
|
+
return "analog", analog_confidence
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _compute_quality_metrics(
|
|
269
|
+
data: NDArray[np.floating[Any]],
|
|
270
|
+
stats: dict[str, float],
|
|
271
|
+
sample_rate: float,
|
|
272
|
+
voltage_low: float,
|
|
273
|
+
voltage_high: float,
|
|
274
|
+
digital_confidence: float,
|
|
275
|
+
) -> dict[str, float]:
|
|
276
|
+
"""Compute signal quality metrics.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
data: Signal data array.
|
|
280
|
+
stats: Basic statistics dictionary.
|
|
281
|
+
sample_rate: Sample rate in Hz.
|
|
282
|
+
voltage_low: Low voltage level.
|
|
283
|
+
voltage_high: High voltage level.
|
|
284
|
+
digital_confidence: Confidence that signal is digital.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Dictionary of quality metrics.
|
|
288
|
+
"""
|
|
189
289
|
noise_level = _estimate_noise_level(data, voltage_low, voltage_high, digital_confidence)
|
|
190
|
-
|
|
290
|
+
return {
|
|
191
291
|
"snr_db": _estimate_snr(data, stats),
|
|
192
292
|
"jitter_ns": _estimate_jitter(data, sample_rate) * 1e9,
|
|
193
293
|
"noise_level": noise_level,
|
|
194
294
|
}
|
|
195
295
|
|
|
196
|
-
|
|
296
|
+
|
|
297
|
+
def _build_alternatives(
|
|
298
|
+
candidates: dict[SignalType, float],
|
|
299
|
+
best_type: SignalType,
|
|
300
|
+
best_confidence: float,
|
|
301
|
+
include_alternatives: bool,
|
|
302
|
+
confidence_threshold: float,
|
|
303
|
+
min_alternatives: int,
|
|
304
|
+
) -> list[tuple[SignalType, float]]:
|
|
305
|
+
"""Build list of alternative signal type suggestions.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
candidates: All candidate types with confidence scores.
|
|
309
|
+
best_type: Best detected signal type.
|
|
310
|
+
best_confidence: Confidence of best type.
|
|
311
|
+
include_alternatives: Whether to include alternatives.
|
|
312
|
+
confidence_threshold: Threshold for including alternatives.
|
|
313
|
+
min_alternatives: Minimum number of alternatives.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
List of (signal_type, confidence) tuples.
|
|
317
|
+
"""
|
|
197
318
|
alternatives: list[tuple[SignalType, float]] = []
|
|
198
|
-
if include_alternatives or best_confidence < confidence_threshold:
|
|
199
|
-
# Include top alternatives (excluding the winner)
|
|
200
|
-
for sig_type, conf in sorted_candidates[1:]:
|
|
201
|
-
if len(alternatives) >= min_alternatives:
|
|
202
|
-
break
|
|
203
|
-
if conf >= 0.3: # Only include reasonable alternatives
|
|
204
|
-
alternatives.append((sig_type, conf))
|
|
205
319
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
320
|
+
if not (include_alternatives or best_confidence < confidence_threshold):
|
|
321
|
+
return alternatives
|
|
322
|
+
|
|
323
|
+
sorted_candidates = sorted(candidates.items(), key=lambda x: x[1], reverse=True)
|
|
324
|
+
|
|
325
|
+
# Include top alternatives (excluding the winner)
|
|
326
|
+
for sig_type, conf in sorted_candidates[1:]:
|
|
327
|
+
if len(alternatives) >= min_alternatives:
|
|
328
|
+
break
|
|
329
|
+
if conf >= 0.3: # Only include reasonable alternatives
|
|
330
|
+
alternatives.append((sig_type, conf))
|
|
331
|
+
|
|
332
|
+
return alternatives
|
|
216
333
|
|
|
217
334
|
|
|
218
335
|
def _estimate_noise_level(
|
oscura/export/__init__.py
CHANGED
|
@@ -5,21 +5,31 @@ to various formats for integration with other tools and workflows.
|
|
|
5
5
|
|
|
6
6
|
Supported export formats:
|
|
7
7
|
- Wireshark Lua dissectors
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- Kaitai Struct definitions (.ksy)
|
|
9
|
+
- Scapy packet layers
|
|
10
10
|
- (Future) C/C++ parser code
|
|
11
11
|
|
|
12
12
|
Example:
|
|
13
|
-
>>> from oscura.export.
|
|
14
|
-
>>> from oscura.
|
|
15
|
-
>>>
|
|
16
|
-
>>>
|
|
17
|
-
>>>
|
|
13
|
+
>>> from oscura.export.wireshark_dissector import WiresharkDissectorGenerator
|
|
14
|
+
>>> from oscura.export.kaitai_struct import KaitaiStructGenerator
|
|
15
|
+
>>> from oscura.export.scapy_layer import ScapyLayerGenerator
|
|
16
|
+
>>> from oscura.workflows.reverse_engineering import ProtocolSpec
|
|
17
|
+
>>> # Generate Wireshark dissector
|
|
18
|
+
>>> wireshark_gen = WiresharkDissectorGenerator(config)
|
|
19
|
+
>>> wireshark_gen.generate(spec, Path("myproto.lua"))
|
|
20
|
+
>>> # Generate Kaitai Struct definition
|
|
21
|
+
>>> kaitai_gen = KaitaiStructGenerator(config)
|
|
22
|
+
>>> kaitai_gen.generate(spec, Path("myproto.ksy"))
|
|
23
|
+
>>> # Generate Scapy layer
|
|
24
|
+
>>> scapy_gen = ScapyLayerGenerator(config)
|
|
25
|
+
>>> scapy_gen.generate(spec, messages, Path("proto_layer.py"))
|
|
18
26
|
"""
|
|
19
27
|
|
|
20
28
|
# Import main exports
|
|
21
|
-
from . import wireshark
|
|
29
|
+
from . import kaitai_struct, scapy_layer, wireshark
|
|
22
30
|
|
|
23
31
|
__all__ = [
|
|
32
|
+
"kaitai_struct",
|
|
33
|
+
"scapy_layer",
|
|
24
34
|
"wireshark",
|
|
25
35
|
]
|