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,381 @@
|
|
|
1
|
+
"""Multi-message pattern learning for CAN bus analysis.
|
|
2
|
+
|
|
3
|
+
This module provides algorithms for discovering relationships between CAN messages,
|
|
4
|
+
including message pairs, sequences, and temporal correlations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from oscura.automotive.can.session import CANSession
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"MessagePair",
|
|
20
|
+
"MessageSequence",
|
|
21
|
+
"PatternAnalyzer",
|
|
22
|
+
"TemporalCorrelation",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class MessagePair:
|
|
28
|
+
"""A pair of CAN messages that frequently occur together.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
id_a: First message arbitration ID.
|
|
32
|
+
id_b: Second message arbitration ID.
|
|
33
|
+
occurrences: Number of times this pair occurred within time window.
|
|
34
|
+
avg_delay_ms: Average time delay from id_a to id_b in milliseconds.
|
|
35
|
+
confidence: Confidence score (0.0-1.0) based on consistency.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
id_a: int
|
|
39
|
+
id_b: int
|
|
40
|
+
occurrences: int
|
|
41
|
+
avg_delay_ms: float
|
|
42
|
+
confidence: float
|
|
43
|
+
|
|
44
|
+
def __repr__(self) -> str:
|
|
45
|
+
"""Human-readable representation."""
|
|
46
|
+
return (
|
|
47
|
+
f"MessagePair(0x{self.id_a:03X} → 0x{self.id_b:03X}, "
|
|
48
|
+
f"occurrences={self.occurrences}, "
|
|
49
|
+
f"delay={self.avg_delay_ms:.2f}ms, "
|
|
50
|
+
f"confidence={self.confidence:.2f})"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class MessageSequence:
|
|
56
|
+
"""A sequence of CAN messages that occur in order.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
ids: List of message arbitration IDs in sequence order.
|
|
60
|
+
occurrences: Number of times this sequence occurred.
|
|
61
|
+
avg_timing: Average delays between consecutive messages in milliseconds.
|
|
62
|
+
support: Support score (0.0-1.0) - fraction of time sequence appears.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
ids: list[int]
|
|
66
|
+
occurrences: int
|
|
67
|
+
avg_timing: list[float]
|
|
68
|
+
support: float
|
|
69
|
+
|
|
70
|
+
def __repr__(self) -> str:
|
|
71
|
+
"""Human-readable representation."""
|
|
72
|
+
id_str = " → ".join(f"0x{id_:03X}" for id_ in self.ids)
|
|
73
|
+
timing_str = " → ".join(f"{t:.1f}ms" for t in self.avg_timing)
|
|
74
|
+
return (
|
|
75
|
+
f"MessageSequence({id_str}, "
|
|
76
|
+
f"occurrences={self.occurrences}, "
|
|
77
|
+
f"timing=[{timing_str}], "
|
|
78
|
+
f"support={self.support:.2f})"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class TemporalCorrelation:
|
|
84
|
+
"""Temporal correlation between two CAN messages.
|
|
85
|
+
|
|
86
|
+
Attributes:
|
|
87
|
+
leader_id: Message ID that typically appears first.
|
|
88
|
+
follower_id: Message ID that typically appears after leader.
|
|
89
|
+
avg_delay_ms: Average delay from leader to follower in milliseconds.
|
|
90
|
+
std_delay_ms: Standard deviation of delay in milliseconds.
|
|
91
|
+
occurrences: Number of times this correlation was observed.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
leader_id: int
|
|
95
|
+
follower_id: int
|
|
96
|
+
avg_delay_ms: float
|
|
97
|
+
std_delay_ms: float
|
|
98
|
+
occurrences: int
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
"""Human-readable representation."""
|
|
102
|
+
return (
|
|
103
|
+
f"TemporalCorrelation(0x{self.leader_id:03X} → 0x{self.follower_id:03X}, "
|
|
104
|
+
f"delay={self.avg_delay_ms:.2f}±{self.std_delay_ms:.2f}ms, "
|
|
105
|
+
f"occurrences={self.occurrences})"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class PatternAnalyzer:
|
|
110
|
+
"""Detect patterns in CAN message sequences.
|
|
111
|
+
|
|
112
|
+
This class provides static methods for discovering relationships between
|
|
113
|
+
CAN messages, useful for understanding message dependencies and control flows.
|
|
114
|
+
|
|
115
|
+
Example - Find message pairs:
|
|
116
|
+
>>> session = CANSession.from_log("capture.blf")
|
|
117
|
+
>>> pairs = PatternAnalyzer.find_message_pairs(session, time_window_ms=100)
|
|
118
|
+
>>> for pair in pairs:
|
|
119
|
+
... print(pair)
|
|
120
|
+
|
|
121
|
+
Example - Find message sequences:
|
|
122
|
+
>>> sequences = PatternAnalyzer.find_message_sequences(
|
|
123
|
+
... session,
|
|
124
|
+
... max_sequence_length=3,
|
|
125
|
+
... time_window_ms=500
|
|
126
|
+
... )
|
|
127
|
+
|
|
128
|
+
Example - Find temporal correlations:
|
|
129
|
+
>>> correlations = PatternAnalyzer.find_temporal_correlations(
|
|
130
|
+
... session,
|
|
131
|
+
... max_delay_ms=100
|
|
132
|
+
... )
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def find_message_pairs(
|
|
137
|
+
session: CANSession,
|
|
138
|
+
time_window_ms: float = 100,
|
|
139
|
+
min_occurrence: int = 3,
|
|
140
|
+
) -> list[MessagePair]:
|
|
141
|
+
"""Find message pairs that frequently occur together.
|
|
142
|
+
|
|
143
|
+
Uses a sliding time window to detect messages that consistently appear
|
|
144
|
+
within a short time of each other. This can reveal request-response
|
|
145
|
+
patterns or coordinated control messages.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
session: CAN session to analyze.
|
|
149
|
+
time_window_ms: Maximum time window in milliseconds to consider
|
|
150
|
+
messages as a pair.
|
|
151
|
+
min_occurrence: Minimum number of occurrences to report a pair.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of MessagePair objects, sorted by occurrence count (descending).
|
|
155
|
+
"""
|
|
156
|
+
time_window_s = time_window_ms / 1000.0
|
|
157
|
+
|
|
158
|
+
# Get all messages sorted by timestamp
|
|
159
|
+
all_messages = sorted(session._messages.messages, key=lambda m: m.timestamp)
|
|
160
|
+
|
|
161
|
+
# Track pairs: (id_a, id_b) -> list of delays
|
|
162
|
+
pair_delays: dict[tuple[int, int], list[float]] = defaultdict(list)
|
|
163
|
+
|
|
164
|
+
# Use sliding window approach
|
|
165
|
+
for i, msg_a in enumerate(all_messages):
|
|
166
|
+
# Look forward in time window
|
|
167
|
+
for msg_b in all_messages[i + 1 :]:
|
|
168
|
+
delay = msg_b.timestamp - msg_a.timestamp
|
|
169
|
+
if delay > time_window_s:
|
|
170
|
+
break # Outside window, stop searching
|
|
171
|
+
|
|
172
|
+
# Don't pair message with itself (same ID)
|
|
173
|
+
if msg_a.arbitration_id != msg_b.arbitration_id:
|
|
174
|
+
pair_key = (msg_a.arbitration_id, msg_b.arbitration_id)
|
|
175
|
+
pair_delays[pair_key].append(delay * 1000) # Convert to ms
|
|
176
|
+
|
|
177
|
+
# Build MessagePair objects
|
|
178
|
+
pairs = []
|
|
179
|
+
for (id_a, id_b), delays in pair_delays.items():
|
|
180
|
+
occurrences = len(delays)
|
|
181
|
+
if occurrences >= min_occurrence:
|
|
182
|
+
avg_delay = np.mean(delays)
|
|
183
|
+
std_delay = np.std(delays)
|
|
184
|
+
|
|
185
|
+
# Confidence based on consistency (lower std = higher confidence)
|
|
186
|
+
# Normalize std by average delay
|
|
187
|
+
if avg_delay > 0:
|
|
188
|
+
cv = std_delay / avg_delay # Coefficient of variation
|
|
189
|
+
confidence = float(1.0 / (1.0 + cv)) # Higher confidence for lower CV
|
|
190
|
+
else:
|
|
191
|
+
confidence = 1.0
|
|
192
|
+
|
|
193
|
+
pairs.append(
|
|
194
|
+
MessagePair(
|
|
195
|
+
id_a=id_a,
|
|
196
|
+
id_b=id_b,
|
|
197
|
+
occurrences=occurrences,
|
|
198
|
+
avg_delay_ms=float(avg_delay),
|
|
199
|
+
confidence=float(confidence),
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Sort by occurrence count (descending)
|
|
204
|
+
pairs.sort(key=lambda p: p.occurrences, reverse=True)
|
|
205
|
+
|
|
206
|
+
return pairs
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def find_message_sequences(
|
|
210
|
+
session: CANSession,
|
|
211
|
+
max_sequence_length: int = 5,
|
|
212
|
+
time_window_ms: float = 500,
|
|
213
|
+
min_support: float = 0.7,
|
|
214
|
+
) -> list[MessageSequence]:
|
|
215
|
+
"""Find message sequences (A → B → C patterns).
|
|
216
|
+
|
|
217
|
+
Uses sequential pattern mining to discover message sequences that
|
|
218
|
+
frequently occur in order. This can reveal multi-step control
|
|
219
|
+
sequences or protocol handshakes.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
session: CAN session to analyze.
|
|
223
|
+
max_sequence_length: Maximum length of sequences to find (2-10).
|
|
224
|
+
time_window_ms: Maximum time window for entire sequence.
|
|
225
|
+
min_support: Minimum support (0.0-1.0) - fraction of occurrences
|
|
226
|
+
relative to most frequent message.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List of MessageSequence objects, sorted by support (descending).
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
ValueError: If max_sequence_length is invalid (<2 or >10) or
|
|
233
|
+
min_support is not in range [0.0, 1.0].
|
|
234
|
+
"""
|
|
235
|
+
if max_sequence_length < 2:
|
|
236
|
+
raise ValueError("max_sequence_length must be at least 2")
|
|
237
|
+
if max_sequence_length > 10:
|
|
238
|
+
raise ValueError("max_sequence_length cannot exceed 10")
|
|
239
|
+
if not 0.0 <= min_support <= 1.0:
|
|
240
|
+
raise ValueError("min_support must be between 0.0 and 1.0")
|
|
241
|
+
|
|
242
|
+
time_window_s = time_window_ms / 1000.0
|
|
243
|
+
|
|
244
|
+
# Get all messages sorted by timestamp
|
|
245
|
+
all_messages = sorted(session._messages.messages, key=lambda m: m.timestamp)
|
|
246
|
+
|
|
247
|
+
if not all_messages:
|
|
248
|
+
return []
|
|
249
|
+
|
|
250
|
+
# Find maximum message frequency (for support calculation)
|
|
251
|
+
id_counts: dict[int, int] = defaultdict(int)
|
|
252
|
+
for msg in all_messages:
|
|
253
|
+
id_counts[msg.arbitration_id] += 1
|
|
254
|
+
max_count = max(id_counts.values()) if id_counts else 1
|
|
255
|
+
|
|
256
|
+
# Mine sequences of increasing length
|
|
257
|
+
sequences: dict[tuple[int, ...], list[list[float]]] = defaultdict(list)
|
|
258
|
+
|
|
259
|
+
# Start with 2-message sequences
|
|
260
|
+
for i, msg_a in enumerate(all_messages):
|
|
261
|
+
# Look forward in time window
|
|
262
|
+
sequence_start = msg_a.timestamp
|
|
263
|
+
current_sequence = [msg_a.arbitration_id]
|
|
264
|
+
timing = []
|
|
265
|
+
|
|
266
|
+
for msg_b in all_messages[i + 1 :]:
|
|
267
|
+
if msg_b.timestamp - sequence_start > time_window_s:
|
|
268
|
+
break
|
|
269
|
+
|
|
270
|
+
# Extend sequence
|
|
271
|
+
current_sequence.append(msg_b.arbitration_id)
|
|
272
|
+
timing.append((msg_b.timestamp - msg_a.timestamp) * 1000) # ms
|
|
273
|
+
|
|
274
|
+
# Record sequences of desired length
|
|
275
|
+
if 2 <= len(current_sequence) <= max_sequence_length:
|
|
276
|
+
seq_key = tuple(current_sequence)
|
|
277
|
+
sequences[seq_key].append(timing.copy())
|
|
278
|
+
|
|
279
|
+
# Update for next iteration
|
|
280
|
+
msg_a = msg_b
|
|
281
|
+
if len(current_sequence) >= max_sequence_length:
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
# Build MessageSequence objects
|
|
285
|
+
result = []
|
|
286
|
+
for seq_ids, timing_lists in sequences.items():
|
|
287
|
+
occurrences = len(timing_lists)
|
|
288
|
+
support = occurrences / max_count
|
|
289
|
+
|
|
290
|
+
if support >= min_support:
|
|
291
|
+
# Calculate average timing between consecutive messages
|
|
292
|
+
avg_timing = []
|
|
293
|
+
if timing_lists:
|
|
294
|
+
# timing_lists[i] has len(seq_ids) - 1 elements
|
|
295
|
+
num_gaps = len(seq_ids) - 1
|
|
296
|
+
for gap_idx in range(num_gaps):
|
|
297
|
+
gap_times = [t[gap_idx] for t in timing_lists if gap_idx < len(t)]
|
|
298
|
+
if gap_times:
|
|
299
|
+
avg_timing.append(float(np.mean(gap_times)))
|
|
300
|
+
|
|
301
|
+
result.append(
|
|
302
|
+
MessageSequence(
|
|
303
|
+
ids=list(seq_ids),
|
|
304
|
+
occurrences=occurrences,
|
|
305
|
+
avg_timing=avg_timing,
|
|
306
|
+
support=float(support),
|
|
307
|
+
)
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Sort by support (descending), then by occurrences
|
|
311
|
+
result.sort(key=lambda s: (s.support, s.occurrences), reverse=True)
|
|
312
|
+
|
|
313
|
+
return result
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
def find_temporal_correlations(
|
|
317
|
+
session: CANSession,
|
|
318
|
+
max_delay_ms: float = 100,
|
|
319
|
+
) -> dict[tuple[int, int], TemporalCorrelation]:
|
|
320
|
+
"""Find temporal correlations between messages.
|
|
321
|
+
|
|
322
|
+
Analyzes timing relationships to determine which messages consistently
|
|
323
|
+
follow others with predictable delays. Unlike message pairs, this focuses
|
|
324
|
+
on statistical correlation rather than simple co-occurrence.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
session: CAN session to analyze.
|
|
328
|
+
max_delay_ms: Maximum delay to consider for correlations.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Dictionary mapping (leader_id, follower_id) to TemporalCorrelation.
|
|
332
|
+
"""
|
|
333
|
+
max_delay_s = max_delay_ms / 1000.0
|
|
334
|
+
|
|
335
|
+
# Get all messages sorted by timestamp
|
|
336
|
+
all_messages = sorted(session._messages.messages, key=lambda m: m.timestamp)
|
|
337
|
+
|
|
338
|
+
# Track correlations: (leader_id, follower_id) -> list of delays
|
|
339
|
+
correlation_delays: dict[tuple[int, int], list[float]] = defaultdict(list)
|
|
340
|
+
|
|
341
|
+
# For each message, find the next occurrence of each other message ID
|
|
342
|
+
# within the time window
|
|
343
|
+
for i, leader_msg in enumerate(all_messages):
|
|
344
|
+
leader_id = leader_msg.arbitration_id
|
|
345
|
+
|
|
346
|
+
# Track which follower IDs we've seen (only count first occurrence)
|
|
347
|
+
seen_followers = set()
|
|
348
|
+
|
|
349
|
+
# Look forward for followers
|
|
350
|
+
for follower_msg in all_messages[i + 1 :]:
|
|
351
|
+
delay = follower_msg.timestamp - leader_msg.timestamp
|
|
352
|
+
if delay > max_delay_s:
|
|
353
|
+
break # Outside window
|
|
354
|
+
|
|
355
|
+
follower_id = follower_msg.arbitration_id
|
|
356
|
+
|
|
357
|
+
# Skip same ID
|
|
358
|
+
if follower_id == leader_id:
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
# Only record first occurrence of each follower ID
|
|
362
|
+
if follower_id not in seen_followers:
|
|
363
|
+
correlation_delays[(leader_id, follower_id)].append(delay * 1000)
|
|
364
|
+
seen_followers.add(follower_id)
|
|
365
|
+
|
|
366
|
+
# Build TemporalCorrelation objects
|
|
367
|
+
correlations = {}
|
|
368
|
+
for (leader_id, follower_id), delays in correlation_delays.items():
|
|
369
|
+
if len(delays) >= 2: # Need at least 2 samples for std
|
|
370
|
+
avg_delay = np.mean(delays)
|
|
371
|
+
std_delay = np.std(delays)
|
|
372
|
+
|
|
373
|
+
correlations[(leader_id, follower_id)] = TemporalCorrelation(
|
|
374
|
+
leader_id=leader_id,
|
|
375
|
+
follower_id=follower_id,
|
|
376
|
+
avg_delay_ms=float(avg_delay),
|
|
377
|
+
std_delay_ms=float(std_delay),
|
|
378
|
+
occurrences=len(delays),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return correlations
|