oscura 0.0.1__py3-none-any.whl → 0.1.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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
"""Unknown signal analysis and reverse engineering.
|
|
2
|
+
|
|
3
|
+
This module provides tools for analyzing signals from unknown systems
|
|
4
|
+
and protocols, including binary field detection and pattern analysis.
|
|
5
|
+
|
|
6
|
+
- UNKNOWN-001: Binary Field Detection
|
|
7
|
+
- UNKNOWN-002: Protocol Auto-Detection with Fuzzy Matching
|
|
8
|
+
- UNKNOWN-003: Unknown Signal Characterization
|
|
9
|
+
- UNKNOWN-004: Pattern Frequency Analysis
|
|
10
|
+
- UNKNOWN-005: Reverse Engineering Workflow
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
>>> from oscura.exploratory.unknown import characterize_unknown_signal
|
|
14
|
+
>>> result = characterize_unknown_signal(trace)
|
|
15
|
+
>>> print(f"Signal type: {result.signal_type}")
|
|
16
|
+
>>> print(f"Suggested protocols: {result.suggested_protocols}")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from numpy.typing import NDArray
|
|
28
|
+
|
|
29
|
+
from oscura.core.types import WaveformTrace
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class BinaryFieldResult:
|
|
34
|
+
"""Result of binary field detection.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
fields: List of detected fields with positions.
|
|
38
|
+
field_count: Total number of fields detected.
|
|
39
|
+
bit_rate: Estimated bit rate in bps.
|
|
40
|
+
encoding: Detected encoding type.
|
|
41
|
+
confidence: Detection confidence.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
fields: list[dict[str, Any]]
|
|
45
|
+
field_count: int
|
|
46
|
+
bit_rate: float | None
|
|
47
|
+
encoding: str
|
|
48
|
+
confidence: float
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def detect_binary_fields(
|
|
52
|
+
trace: WaveformTrace,
|
|
53
|
+
*,
|
|
54
|
+
min_field_bits: int = 4,
|
|
55
|
+
max_gap_ratio: float = 2.0,
|
|
56
|
+
) -> BinaryFieldResult:
|
|
57
|
+
"""Detect binary fields in unknown signal per UNKNOWN-001.
|
|
58
|
+
|
|
59
|
+
Analyzes signal for structured binary data patterns including
|
|
60
|
+
start/stop markers, length fields, and data payloads.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
trace: Signal trace to analyze.
|
|
64
|
+
min_field_bits: Minimum bits to consider a field.
|
|
65
|
+
max_gap_ratio: Maximum gap ratio for field boundaries.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
BinaryFieldResult with detected fields.
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> result = detect_binary_fields(trace)
|
|
72
|
+
>>> for field in result.fields:
|
|
73
|
+
... print(f"Field at {field['start_sample']}: {field['length']} bits")
|
|
74
|
+
|
|
75
|
+
References:
|
|
76
|
+
UNKNOWN-001: Binary Field Detection
|
|
77
|
+
"""
|
|
78
|
+
data = trace.data
|
|
79
|
+
sample_rate = trace.metadata.sample_rate
|
|
80
|
+
|
|
81
|
+
# Threshold for digital conversion
|
|
82
|
+
v_min = np.percentile(data, 5)
|
|
83
|
+
v_max = np.percentile(data, 95)
|
|
84
|
+
threshold = (v_min + v_max) / 2
|
|
85
|
+
|
|
86
|
+
# Convert to digital
|
|
87
|
+
digital = (data > threshold).astype(int)
|
|
88
|
+
|
|
89
|
+
# Find edges
|
|
90
|
+
edges = np.where(np.diff(digital) != 0)[0]
|
|
91
|
+
|
|
92
|
+
if len(edges) < 2:
|
|
93
|
+
return BinaryFieldResult(
|
|
94
|
+
fields=[],
|
|
95
|
+
field_count=0,
|
|
96
|
+
bit_rate=None,
|
|
97
|
+
encoding="unknown",
|
|
98
|
+
confidence=0.0,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Estimate bit period from edge spacing
|
|
102
|
+
edge_gaps = np.diff(edges)
|
|
103
|
+
median_gap = np.median(edge_gaps)
|
|
104
|
+
bit_period = median_gap
|
|
105
|
+
|
|
106
|
+
# Group edges into fields
|
|
107
|
+
fields = []
|
|
108
|
+
current_field_start = edges[0]
|
|
109
|
+
current_field_edges = [edges[0]]
|
|
110
|
+
|
|
111
|
+
for i in range(1, len(edges)):
|
|
112
|
+
gap = edges[i] - edges[i - 1]
|
|
113
|
+
|
|
114
|
+
if gap > max_gap_ratio * bit_period:
|
|
115
|
+
# End current field
|
|
116
|
+
if len(current_field_edges) >= min_field_bits:
|
|
117
|
+
current_field_edges[-1] - current_field_edges[0]
|
|
118
|
+
n_bits = len(current_field_edges) - 1
|
|
119
|
+
|
|
120
|
+
# Extract bit pattern
|
|
121
|
+
bits = []
|
|
122
|
+
for j in range(len(current_field_edges) - 1):
|
|
123
|
+
start = current_field_edges[j]
|
|
124
|
+
end = current_field_edges[j + 1]
|
|
125
|
+
mid = (start + end) // 2
|
|
126
|
+
bits.append(digital[mid])
|
|
127
|
+
|
|
128
|
+
fields.append(
|
|
129
|
+
{
|
|
130
|
+
"start_sample": int(current_field_start),
|
|
131
|
+
"end_sample": int(current_field_edges[-1]),
|
|
132
|
+
"length": n_bits,
|
|
133
|
+
"bits": bits,
|
|
134
|
+
"timestamp": current_field_start / sample_rate,
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Start new field
|
|
139
|
+
current_field_start = edges[i]
|
|
140
|
+
current_field_edges = [edges[i]]
|
|
141
|
+
else:
|
|
142
|
+
current_field_edges.append(edges[i])
|
|
143
|
+
|
|
144
|
+
# Handle last field
|
|
145
|
+
if len(current_field_edges) >= min_field_bits:
|
|
146
|
+
current_field_edges[-1] - current_field_edges[0]
|
|
147
|
+
n_bits = len(current_field_edges) - 1
|
|
148
|
+
bits = []
|
|
149
|
+
for j in range(len(current_field_edges) - 1):
|
|
150
|
+
start = current_field_edges[j]
|
|
151
|
+
end = current_field_edges[j + 1]
|
|
152
|
+
mid = (start + end) // 2
|
|
153
|
+
bits.append(digital[mid])
|
|
154
|
+
|
|
155
|
+
fields.append(
|
|
156
|
+
{
|
|
157
|
+
"start_sample": int(current_field_start),
|
|
158
|
+
"end_sample": int(current_field_edges[-1]),
|
|
159
|
+
"length": n_bits,
|
|
160
|
+
"bits": bits,
|
|
161
|
+
"timestamp": current_field_start / sample_rate,
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Estimate bit rate
|
|
166
|
+
bit_rate = sample_rate / bit_period if bit_period > 0 else None
|
|
167
|
+
|
|
168
|
+
# Detect encoding
|
|
169
|
+
encoding = _detect_encoding(digital, edges, bit_period)
|
|
170
|
+
|
|
171
|
+
# Calculate confidence
|
|
172
|
+
confidence = min(1.0, len(fields) / 10.0) * 0.8
|
|
173
|
+
if bit_rate is not None:
|
|
174
|
+
confidence += 0.2
|
|
175
|
+
|
|
176
|
+
return BinaryFieldResult(
|
|
177
|
+
fields=fields,
|
|
178
|
+
field_count=len(fields),
|
|
179
|
+
bit_rate=bit_rate,
|
|
180
|
+
encoding=encoding,
|
|
181
|
+
confidence=confidence,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _detect_encoding(
|
|
186
|
+
digital: NDArray[np.int_],
|
|
187
|
+
edges: NDArray[np.int_],
|
|
188
|
+
bit_period: float,
|
|
189
|
+
) -> str:
|
|
190
|
+
"""Detect signal encoding type.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
digital: Digital signal.
|
|
194
|
+
edges: Edge positions.
|
|
195
|
+
bit_period: Estimated bit period.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Encoding type name.
|
|
199
|
+
"""
|
|
200
|
+
if len(edges) < 4:
|
|
201
|
+
return "unknown"
|
|
202
|
+
|
|
203
|
+
# Analyze edge spacing patterns
|
|
204
|
+
gaps = np.diff(edges)
|
|
205
|
+
|
|
206
|
+
# Check for Manchester (edges every half bit)
|
|
207
|
+
if np.std(gaps) < bit_period * 0.3:
|
|
208
|
+
return "manchester"
|
|
209
|
+
|
|
210
|
+
# Check for NRZ (edges at bit boundaries)
|
|
211
|
+
normalized_gaps = gaps / bit_period
|
|
212
|
+
integer_gaps = np.round(normalized_gaps)
|
|
213
|
+
residuals = np.abs(normalized_gaps - integer_gaps)
|
|
214
|
+
|
|
215
|
+
if np.mean(residuals) < 0.2:
|
|
216
|
+
return "nrz"
|
|
217
|
+
|
|
218
|
+
# Check for NRZI
|
|
219
|
+
if np.mean(normalized_gaps > 0.8) > 0.7:
|
|
220
|
+
return "nrzi"
|
|
221
|
+
|
|
222
|
+
return "unknown"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class UnknownSignalCharacterization:
|
|
227
|
+
"""Comprehensive characterization of unknown signal.
|
|
228
|
+
|
|
229
|
+
Attributes:
|
|
230
|
+
signal_type: 'digital', 'analog', or 'mixed'.
|
|
231
|
+
is_periodic: True if signal is periodic.
|
|
232
|
+
fundamental_frequency: Fundamental frequency if periodic.
|
|
233
|
+
dc_offset: DC offset voltage.
|
|
234
|
+
amplitude: Signal amplitude.
|
|
235
|
+
rise_time: Estimated rise time.
|
|
236
|
+
fall_time: Estimated fall time.
|
|
237
|
+
suggested_protocols: List of possible protocols.
|
|
238
|
+
noise_floor: Estimated noise floor.
|
|
239
|
+
snr_db: Signal-to-noise ratio in dB.
|
|
240
|
+
features: Dictionary of extracted features.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
signal_type: Literal["digital", "analog", "mixed"]
|
|
244
|
+
is_periodic: bool
|
|
245
|
+
fundamental_frequency: float | None
|
|
246
|
+
dc_offset: float
|
|
247
|
+
amplitude: float
|
|
248
|
+
rise_time: float | None
|
|
249
|
+
fall_time: float | None
|
|
250
|
+
suggested_protocols: list[tuple[str, float]]
|
|
251
|
+
noise_floor: float
|
|
252
|
+
snr_db: float
|
|
253
|
+
features: dict[str, Any] = field(default_factory=dict)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def characterize_unknown_signal(
|
|
257
|
+
trace: WaveformTrace,
|
|
258
|
+
) -> UnknownSignalCharacterization:
|
|
259
|
+
"""Comprehensive characterization of unknown signal per UNKNOWN-003.
|
|
260
|
+
|
|
261
|
+
Analyzes signal characteristics to determine type, periodicity,
|
|
262
|
+
and suggest possible protocols.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
trace: Signal trace to characterize.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
UnknownSignalCharacterization with all extracted features.
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
>>> result = characterize_unknown_signal(trace)
|
|
272
|
+
>>> print(f"Signal type: {result.signal_type}")
|
|
273
|
+
>>> print(f"Periodic: {result.is_periodic}")
|
|
274
|
+
>>> for protocol, confidence in result.suggested_protocols:
|
|
275
|
+
... print(f" {protocol}: {confidence:.1%}")
|
|
276
|
+
|
|
277
|
+
References:
|
|
278
|
+
UNKNOWN-003: Unknown Signal Characterization
|
|
279
|
+
"""
|
|
280
|
+
data = trace.data
|
|
281
|
+
sample_rate = trace.metadata.sample_rate
|
|
282
|
+
|
|
283
|
+
# Handle edge case of very short traces
|
|
284
|
+
if len(data) < 2:
|
|
285
|
+
return UnknownSignalCharacterization(
|
|
286
|
+
signal_type="analog",
|
|
287
|
+
is_periodic=False,
|
|
288
|
+
fundamental_frequency=None,
|
|
289
|
+
dc_offset=float(data[0]) if len(data) > 0 else 0.0,
|
|
290
|
+
amplitude=0.0,
|
|
291
|
+
rise_time=None,
|
|
292
|
+
fall_time=None,
|
|
293
|
+
suggested_protocols=[],
|
|
294
|
+
noise_floor=0.0,
|
|
295
|
+
snr_db=float("inf"),
|
|
296
|
+
features={},
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Basic statistics
|
|
300
|
+
v_min = np.min(data)
|
|
301
|
+
v_max = np.max(data)
|
|
302
|
+
v_mean = np.mean(data)
|
|
303
|
+
v_std = np.std(data)
|
|
304
|
+
|
|
305
|
+
dc_offset = v_mean
|
|
306
|
+
amplitude = (v_max - v_min) / 2
|
|
307
|
+
|
|
308
|
+
# Determine signal type
|
|
309
|
+
# Digital signals have bimodal distribution
|
|
310
|
+
hist, bin_edges = np.histogram(data, bins=50)
|
|
311
|
+
centers = (bin_edges[:-1] + bin_edges[1:]) / 2
|
|
312
|
+
|
|
313
|
+
# Find peaks in histogram
|
|
314
|
+
peaks = []
|
|
315
|
+
for i in range(1, len(hist) - 1):
|
|
316
|
+
if hist[i] > hist[i - 1] and hist[i] > hist[i + 1] and hist[i] > 0.1 * np.max(hist):
|
|
317
|
+
peaks.append((centers[i], hist[i]))
|
|
318
|
+
|
|
319
|
+
if len(peaks) >= 4:
|
|
320
|
+
# Many peaks suggest analog signal (e.g., sine wave with noisy histogram)
|
|
321
|
+
signal_type: Literal["digital", "analog", "mixed"] = "analog"
|
|
322
|
+
elif len(peaks) == 2 or len(peaks) == 3:
|
|
323
|
+
# Two peaks suggest digital (bimodal), but check if they're well-separated
|
|
324
|
+
peak_positions = [p[0] for p in peaks]
|
|
325
|
+
# Normalize peak positions to 0-1 range
|
|
326
|
+
normalized_peaks = [(p - v_min) / (v_max - v_min) for p in peak_positions]
|
|
327
|
+
|
|
328
|
+
# If peaks are well-separated (one < 0.4, one > 0.6), likely digital
|
|
329
|
+
has_low_peak = any(p < 0.4 for p in normalized_peaks)
|
|
330
|
+
has_high_peak = any(p > 0.6 for p in normalized_peaks)
|
|
331
|
+
|
|
332
|
+
if has_low_peak and has_high_peak:
|
|
333
|
+
signal_type = "digital"
|
|
334
|
+
else:
|
|
335
|
+
# Peaks not well separated, likely analog
|
|
336
|
+
signal_type = "analog"
|
|
337
|
+
elif len(peaks) == 1:
|
|
338
|
+
# Check for modulated signal
|
|
339
|
+
signal_type = "mixed" if v_std > 0.2 * amplitude else "analog"
|
|
340
|
+
else:
|
|
341
|
+
signal_type = "analog"
|
|
342
|
+
|
|
343
|
+
# Check periodicity via FFT
|
|
344
|
+
from scipy import signal as sp_signal
|
|
345
|
+
|
|
346
|
+
n = len(data)
|
|
347
|
+
# Need at least 4 samples for meaningful FFT analysis
|
|
348
|
+
if n >= 4:
|
|
349
|
+
f, psd = sp_signal.welch(data, fs=sample_rate, nperseg=min(4096, n))
|
|
350
|
+
|
|
351
|
+
# Find dominant frequency (excluding DC)
|
|
352
|
+
psd_no_dc = psd.copy()
|
|
353
|
+
psd_no_dc[0] = 0
|
|
354
|
+
|
|
355
|
+
if len(psd_no_dc) > 0 and np.any(psd_no_dc > 0):
|
|
356
|
+
peak_idx = np.argmax(psd_no_dc)
|
|
357
|
+
mean_psd = np.mean(psd_no_dc[psd_no_dc > 0]) if np.any(psd_no_dc > 0) else 0
|
|
358
|
+
fundamental_frequency = f[peak_idx] if psd_no_dc[peak_idx] > 10 * mean_psd else None
|
|
359
|
+
else:
|
|
360
|
+
fundamental_frequency = None
|
|
361
|
+
|
|
362
|
+
is_periodic = fundamental_frequency is not None
|
|
363
|
+
else:
|
|
364
|
+
fundamental_frequency = None
|
|
365
|
+
is_periodic = False
|
|
366
|
+
|
|
367
|
+
# Estimate noise floor
|
|
368
|
+
if n >= 4:
|
|
369
|
+
noise_floor = np.median(np.sort(psd)[: len(psd) // 4]) if len(psd) > 0 else 0.0
|
|
370
|
+
signal_power = np.max(psd) - noise_floor if len(psd) > 0 else 0.0
|
|
371
|
+
else:
|
|
372
|
+
noise_floor = 0.0
|
|
373
|
+
signal_power = 0.0
|
|
374
|
+
snr_db = 10 * np.log10(signal_power / noise_floor) if noise_floor > 0 else 0
|
|
375
|
+
|
|
376
|
+
# Estimate rise/fall times for digital signals
|
|
377
|
+
rise_time = None
|
|
378
|
+
fall_time = None
|
|
379
|
+
|
|
380
|
+
if signal_type == "digital":
|
|
381
|
+
threshold_low = v_min + 0.1 * (v_max - v_min)
|
|
382
|
+
threshold_high = v_min + 0.9 * (v_max - v_min)
|
|
383
|
+
|
|
384
|
+
# Find rising edges
|
|
385
|
+
rising_times = []
|
|
386
|
+
falling_times = []
|
|
387
|
+
|
|
388
|
+
for i in range(1, len(data) - 1):
|
|
389
|
+
if data[i - 1] < threshold_low and data[i + 1] > threshold_high:
|
|
390
|
+
# Rising edge
|
|
391
|
+
rising_times.append(1 / sample_rate)
|
|
392
|
+
elif data[i - 1] > threshold_high and data[i + 1] < threshold_low:
|
|
393
|
+
# Falling edge
|
|
394
|
+
falling_times.append(1 / sample_rate)
|
|
395
|
+
|
|
396
|
+
if rising_times:
|
|
397
|
+
rise_time = float(np.median(rising_times))
|
|
398
|
+
if falling_times:
|
|
399
|
+
fall_time = float(np.median(falling_times))
|
|
400
|
+
|
|
401
|
+
# Suggest protocols
|
|
402
|
+
suggested_protocols = _suggest_protocols(signal_type, fundamental_frequency, sample_rate, data)
|
|
403
|
+
|
|
404
|
+
# Collect features
|
|
405
|
+
features = {
|
|
406
|
+
"v_min": v_min,
|
|
407
|
+
"v_max": v_max,
|
|
408
|
+
"v_mean": v_mean,
|
|
409
|
+
"v_std": v_std,
|
|
410
|
+
"crest_factor": v_max / np.sqrt(np.mean(data**2)) if np.mean(data**2) > 0 else 0,
|
|
411
|
+
"n_peaks": len(peaks),
|
|
412
|
+
"peak_positions": [p[0] for p in peaks],
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return UnknownSignalCharacterization(
|
|
416
|
+
signal_type=signal_type,
|
|
417
|
+
is_periodic=is_periodic,
|
|
418
|
+
fundamental_frequency=fundamental_frequency,
|
|
419
|
+
dc_offset=dc_offset,
|
|
420
|
+
amplitude=amplitude,
|
|
421
|
+
rise_time=rise_time,
|
|
422
|
+
fall_time=fall_time,
|
|
423
|
+
suggested_protocols=suggested_protocols,
|
|
424
|
+
noise_floor=noise_floor,
|
|
425
|
+
snr_db=snr_db,
|
|
426
|
+
features=features,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _suggest_protocols(
|
|
431
|
+
signal_type: str,
|
|
432
|
+
frequency: float | None,
|
|
433
|
+
sample_rate: float,
|
|
434
|
+
data: NDArray[np.float64],
|
|
435
|
+
) -> list[tuple[str, float]]:
|
|
436
|
+
"""Suggest possible protocols based on signal characteristics.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
signal_type: Signal type (digital/analog/mixed).
|
|
440
|
+
frequency: Fundamental frequency.
|
|
441
|
+
sample_rate: Sample rate.
|
|
442
|
+
data: Signal data.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
List of (protocol_name, confidence) tuples.
|
|
446
|
+
"""
|
|
447
|
+
suggestions = [] # type: ignore[var-annotated]
|
|
448
|
+
|
|
449
|
+
if signal_type != "digital":
|
|
450
|
+
return suggestions
|
|
451
|
+
|
|
452
|
+
# Estimate bit rate from signal
|
|
453
|
+
v_min = np.percentile(data, 5)
|
|
454
|
+
v_max = np.percentile(data, 95)
|
|
455
|
+
threshold = (v_min + v_max) / 2
|
|
456
|
+
digital = data > threshold
|
|
457
|
+
edges = np.where(np.diff(digital.astype(int)) != 0)[0]
|
|
458
|
+
|
|
459
|
+
if len(edges) < 2:
|
|
460
|
+
return suggestions
|
|
461
|
+
|
|
462
|
+
median_gap = np.median(np.diff(edges))
|
|
463
|
+
estimated_bitrate = sample_rate / median_gap
|
|
464
|
+
|
|
465
|
+
# Check common baud rates for UART
|
|
466
|
+
uart_rates = [9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600]
|
|
467
|
+
for rate in uart_rates:
|
|
468
|
+
ratio = estimated_bitrate / rate
|
|
469
|
+
if 0.9 <= ratio <= 1.1:
|
|
470
|
+
suggestions.append(("UART", 0.7 + 0.3 * (1 - abs(1 - ratio))))
|
|
471
|
+
break
|
|
472
|
+
|
|
473
|
+
# Check for I2C (two-wire, specific timing)
|
|
474
|
+
if 50e3 <= estimated_bitrate <= 400e3:
|
|
475
|
+
suggestions.append(("I2C", 0.5))
|
|
476
|
+
elif 400e3 < estimated_bitrate <= 3.4e6:
|
|
477
|
+
suggestions.append(("I2C Fast Mode", 0.5))
|
|
478
|
+
|
|
479
|
+
# Check for SPI (higher speeds)
|
|
480
|
+
if estimated_bitrate >= 1e6:
|
|
481
|
+
suggestions.append(("SPI", 0.4))
|
|
482
|
+
|
|
483
|
+
# Check for CAN
|
|
484
|
+
can_rates = [125e3, 250e3, 500e3, 1e6]
|
|
485
|
+
for rate in can_rates: # type: ignore[assignment]
|
|
486
|
+
if 0.9 <= estimated_bitrate / rate <= 1.1:
|
|
487
|
+
suggestions.append(("CAN", 0.6))
|
|
488
|
+
break
|
|
489
|
+
|
|
490
|
+
# Sort by confidence
|
|
491
|
+
suggestions.sort(key=lambda x: x[1], reverse=True)
|
|
492
|
+
|
|
493
|
+
return suggestions
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@dataclass
|
|
497
|
+
class PatternFrequencyResult:
|
|
498
|
+
"""Result of pattern frequency analysis.
|
|
499
|
+
|
|
500
|
+
Attributes:
|
|
501
|
+
patterns: Dictionary of pattern to count.
|
|
502
|
+
most_common: List of (pattern, count) for most common patterns.
|
|
503
|
+
entropy: Shannon entropy of pattern distribution.
|
|
504
|
+
repetition_rate: Rate of pattern repetition.
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
patterns: dict[tuple[int, ...], int]
|
|
508
|
+
most_common: list[tuple[tuple[int, ...], int]]
|
|
509
|
+
entropy: float
|
|
510
|
+
repetition_rate: float
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def analyze_pattern_frequency(
|
|
514
|
+
trace: WaveformTrace,
|
|
515
|
+
*,
|
|
516
|
+
pattern_length: int = 8,
|
|
517
|
+
min_occurrences: int = 2,
|
|
518
|
+
) -> PatternFrequencyResult:
|
|
519
|
+
"""Analyze frequency of bit patterns per UNKNOWN-004.
|
|
520
|
+
|
|
521
|
+
Identifies recurring patterns that may indicate protocol structure
|
|
522
|
+
or data framing.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
trace: Signal trace to analyze.
|
|
526
|
+
pattern_length: Length of patterns to search for.
|
|
527
|
+
min_occurrences: Minimum occurrences to report.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
PatternFrequencyResult with pattern statistics.
|
|
531
|
+
|
|
532
|
+
Example:
|
|
533
|
+
>>> result = analyze_pattern_frequency(trace, pattern_length=8)
|
|
534
|
+
>>> for pattern, count in result.most_common[:5]:
|
|
535
|
+
... print(f"Pattern {pattern}: {count} occurrences")
|
|
536
|
+
|
|
537
|
+
References:
|
|
538
|
+
UNKNOWN-004: Pattern Frequency Analysis
|
|
539
|
+
"""
|
|
540
|
+
data = trace.data
|
|
541
|
+
|
|
542
|
+
# Convert to digital
|
|
543
|
+
v_min = np.percentile(data, 5)
|
|
544
|
+
v_max = np.percentile(data, 95)
|
|
545
|
+
threshold = (v_min + v_max) / 2
|
|
546
|
+
digital = (data > threshold).astype(int)
|
|
547
|
+
|
|
548
|
+
# Find bit boundaries from edges
|
|
549
|
+
edges = np.where(np.diff(digital) != 0)[0]
|
|
550
|
+
|
|
551
|
+
if len(edges) < 2:
|
|
552
|
+
return PatternFrequencyResult(
|
|
553
|
+
patterns={},
|
|
554
|
+
most_common=[],
|
|
555
|
+
entropy=0.0,
|
|
556
|
+
repetition_rate=0.0,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
# Estimate bit period
|
|
560
|
+
median_gap = np.median(np.diff(edges))
|
|
561
|
+
|
|
562
|
+
# Sample at bit centers
|
|
563
|
+
bits = []
|
|
564
|
+
sample_pos = edges[0] + median_gap / 2
|
|
565
|
+
|
|
566
|
+
while sample_pos < len(digital):
|
|
567
|
+
idx = int(sample_pos)
|
|
568
|
+
if idx < len(digital):
|
|
569
|
+
bits.append(digital[idx])
|
|
570
|
+
sample_pos += median_gap
|
|
571
|
+
|
|
572
|
+
# Count patterns
|
|
573
|
+
patterns: dict[tuple[int, ...], int] = {}
|
|
574
|
+
|
|
575
|
+
for i in range(len(bits) - pattern_length + 1):
|
|
576
|
+
pattern = tuple(bits[i : i + pattern_length])
|
|
577
|
+
patterns[pattern] = patterns.get(pattern, 0) + 1
|
|
578
|
+
|
|
579
|
+
# Filter by minimum occurrences
|
|
580
|
+
patterns = {p: c for p, c in patterns.items() if c >= min_occurrences}
|
|
581
|
+
|
|
582
|
+
# Find most common
|
|
583
|
+
most_common = sorted(patterns.items(), key=lambda x: x[1], reverse=True)[:20]
|
|
584
|
+
|
|
585
|
+
# Calculate entropy
|
|
586
|
+
total = sum(patterns.values())
|
|
587
|
+
if total > 0:
|
|
588
|
+
probs = np.array(list(patterns.values())) / total
|
|
589
|
+
entropy = -np.sum(probs * np.log2(probs + 1e-10))
|
|
590
|
+
else:
|
|
591
|
+
entropy = 0.0
|
|
592
|
+
|
|
593
|
+
# Repetition rate
|
|
594
|
+
repetition_rate = 1 - len(patterns) / total if total > 0 else 0.0
|
|
595
|
+
|
|
596
|
+
return PatternFrequencyResult(
|
|
597
|
+
patterns=patterns,
|
|
598
|
+
most_common=most_common,
|
|
599
|
+
entropy=entropy,
|
|
600
|
+
repetition_rate=repetition_rate,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
@dataclass
|
|
605
|
+
class ReverseEngineeringResult:
|
|
606
|
+
"""Result of reverse engineering workflow.
|
|
607
|
+
|
|
608
|
+
Attributes:
|
|
609
|
+
signal_char: Signal characterization.
|
|
610
|
+
binary_fields: Detected binary fields.
|
|
611
|
+
pattern_analysis: Pattern frequency analysis.
|
|
612
|
+
protocol_hypothesis: Most likely protocol.
|
|
613
|
+
confidence: Overall confidence.
|
|
614
|
+
recommendations: List of next steps.
|
|
615
|
+
"""
|
|
616
|
+
|
|
617
|
+
signal_char: UnknownSignalCharacterization
|
|
618
|
+
binary_fields: BinaryFieldResult
|
|
619
|
+
pattern_analysis: PatternFrequencyResult
|
|
620
|
+
protocol_hypothesis: str
|
|
621
|
+
confidence: float
|
|
622
|
+
recommendations: list[str]
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def reverse_engineer_protocol(
|
|
626
|
+
trace: WaveformTrace,
|
|
627
|
+
) -> ReverseEngineeringResult:
|
|
628
|
+
"""Comprehensive reverse engineering workflow per UNKNOWN-005.
|
|
629
|
+
|
|
630
|
+
Combines all unknown signal analysis techniques to build
|
|
631
|
+
a hypothesis about the protocol in use.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
trace: Signal trace to reverse engineer.
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
ReverseEngineeringResult with comprehensive analysis.
|
|
638
|
+
|
|
639
|
+
Example:
|
|
640
|
+
>>> result = reverse_engineer_protocol(trace)
|
|
641
|
+
>>> print(f"Protocol hypothesis: {result.protocol_hypothesis}")
|
|
642
|
+
>>> print(f"Confidence: {result.confidence:.1%}")
|
|
643
|
+
>>> for rec in result.recommendations:
|
|
644
|
+
... print(f"- {rec}")
|
|
645
|
+
|
|
646
|
+
References:
|
|
647
|
+
UNKNOWN-005: Reverse Engineering Workflow
|
|
648
|
+
"""
|
|
649
|
+
# Run all analysis steps
|
|
650
|
+
signal_char = characterize_unknown_signal(trace)
|
|
651
|
+
binary_fields = detect_binary_fields(trace)
|
|
652
|
+
pattern_analysis = analyze_pattern_frequency(trace)
|
|
653
|
+
|
|
654
|
+
# Build hypothesis
|
|
655
|
+
protocol_hypothesis = "Unknown"
|
|
656
|
+
confidence = 0.0
|
|
657
|
+
|
|
658
|
+
if signal_char.suggested_protocols:
|
|
659
|
+
protocol_hypothesis = signal_char.suggested_protocols[0][0]
|
|
660
|
+
confidence = signal_char.suggested_protocols[0][1]
|
|
661
|
+
|
|
662
|
+
# Generate recommendations
|
|
663
|
+
recommendations = []
|
|
664
|
+
|
|
665
|
+
if signal_char.signal_type != "digital":
|
|
666
|
+
recommendations.append("Signal appears analog - check if correct probe/channel")
|
|
667
|
+
|
|
668
|
+
if binary_fields.field_count == 0:
|
|
669
|
+
recommendations.append("No binary fields detected - try adjusting threshold")
|
|
670
|
+
|
|
671
|
+
if binary_fields.encoding == "manchester":
|
|
672
|
+
recommendations.append("Manchester encoding detected - common in Ethernet, 1-Wire")
|
|
673
|
+
|
|
674
|
+
if pattern_analysis.repetition_rate > 0.5:
|
|
675
|
+
recommendations.append(
|
|
676
|
+
"High pattern repetition - likely periodic protocol (e.g., I2C polling)"
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
if signal_char.snr_db < 10:
|
|
680
|
+
recommendations.append("Low SNR - consider using averaging or filtering")
|
|
681
|
+
|
|
682
|
+
if not signal_char.suggested_protocols:
|
|
683
|
+
recommendations.append("No protocol match - try capturing with different settings")
|
|
684
|
+
|
|
685
|
+
if binary_fields.bit_rate is not None:
|
|
686
|
+
recommendations.append(f"Estimated bit rate: {binary_fields.bit_rate:.0f} bps")
|
|
687
|
+
|
|
688
|
+
return ReverseEngineeringResult(
|
|
689
|
+
signal_char=signal_char,
|
|
690
|
+
binary_fields=binary_fields,
|
|
691
|
+
pattern_analysis=pattern_analysis,
|
|
692
|
+
protocol_hypothesis=protocol_hypothesis,
|
|
693
|
+
confidence=confidence,
|
|
694
|
+
recommendations=recommendations,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
__all__ = [
|
|
699
|
+
"BinaryFieldResult",
|
|
700
|
+
"PatternFrequencyResult",
|
|
701
|
+
"ReverseEngineeringResult",
|
|
702
|
+
"UnknownSignalCharacterization",
|
|
703
|
+
"analyze_pattern_frequency",
|
|
704
|
+
"characterize_unknown_signal",
|
|
705
|
+
"detect_binary_fields",
|
|
706
|
+
"reverse_engineer_protocol",
|
|
707
|
+
]
|