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,452 @@
|
|
|
1
|
+
"""CAN reverse engineering session.
|
|
2
|
+
|
|
3
|
+
This module provides the main user-facing API for CAN bus reverse engineering,
|
|
4
|
+
centered around the CANSession class which manages message collections and
|
|
5
|
+
provides discovery-oriented analysis workflows.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from oscura.automotive.can.analysis import MessageAnalyzer
|
|
16
|
+
from oscura.automotive.can.models import (
|
|
17
|
+
CANMessage,
|
|
18
|
+
CANMessageList,
|
|
19
|
+
MessageAnalysis,
|
|
20
|
+
)
|
|
21
|
+
from oscura.automotive.can.patterns import (
|
|
22
|
+
MessagePair,
|
|
23
|
+
MessageSequence,
|
|
24
|
+
PatternAnalyzer,
|
|
25
|
+
TemporalCorrelation,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from oscura.automotive.can.message_wrapper import CANMessageWrapper
|
|
30
|
+
from oscura.automotive.can.stimulus_response import StimulusResponseReport
|
|
31
|
+
from oscura.inference.state_machine import FiniteAutomaton
|
|
32
|
+
|
|
33
|
+
__all__ = ["CANSession"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CANSession:
|
|
37
|
+
"""CAN bus reverse engineering session.
|
|
38
|
+
|
|
39
|
+
This is the primary API for discovering and analyzing unknown CAN bus
|
|
40
|
+
protocols. It provides:
|
|
41
|
+
- Message inventory and filtering
|
|
42
|
+
- Per-message statistical analysis
|
|
43
|
+
- Discovery-oriented workflows
|
|
44
|
+
- Hypothesis testing
|
|
45
|
+
- Documentation generation
|
|
46
|
+
|
|
47
|
+
Example - Discovery workflow:
|
|
48
|
+
>>> session = CANSession.from_log("capture.blf")
|
|
49
|
+
>>> inventory = session.inventory()
|
|
50
|
+
>>> print(inventory)
|
|
51
|
+
>>>
|
|
52
|
+
>>> # Focus on a specific message
|
|
53
|
+
>>> msg = session.message(0x280)
|
|
54
|
+
>>> analysis = msg.analyze()
|
|
55
|
+
>>> print(analysis.summary())
|
|
56
|
+
>>>
|
|
57
|
+
>>> # Test hypothesis
|
|
58
|
+
>>> hypothesis = msg.test_hypothesis(
|
|
59
|
+
... signal_name="rpm",
|
|
60
|
+
... start_byte=2,
|
|
61
|
+
... bit_length=16,
|
|
62
|
+
... scale=0.25
|
|
63
|
+
... )
|
|
64
|
+
|
|
65
|
+
Example - Known protocol decoding:
|
|
66
|
+
>>> session = CANSession.from_log("capture.blf")
|
|
67
|
+
>>> from oscura.automotive.dbc import load_dbc
|
|
68
|
+
>>> dbc = load_dbc("vehicle.dbc")
|
|
69
|
+
>>> decoded = session.decode(dbc)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, messages: CANMessageList | None = None):
|
|
73
|
+
"""Initialize CAN session.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
messages: Initial message collection (optional).
|
|
77
|
+
"""
|
|
78
|
+
self._messages = messages or CANMessageList()
|
|
79
|
+
self._analyses_cache: dict[int, MessageAnalysis] = {}
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_log(cls, file_path: Path | str) -> CANSession:
|
|
83
|
+
"""Create session from automotive log file.
|
|
84
|
+
|
|
85
|
+
Automatically detects file format (BLF, ASC, MDF, CSV) and loads.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
file_path: Path to log file.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
New CANSession with loaded messages.
|
|
92
|
+
"""
|
|
93
|
+
from oscura.automotive.loaders import load_automotive_log
|
|
94
|
+
|
|
95
|
+
messages = load_automotive_log(file_path)
|
|
96
|
+
return cls(messages=messages)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_messages(cls, messages: list[CANMessage]) -> CANSession:
|
|
100
|
+
"""Create session from list of CAN messages.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
messages: List of CAN messages.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
New CANSession.
|
|
107
|
+
"""
|
|
108
|
+
msg_list = CANMessageList(messages=messages)
|
|
109
|
+
return cls(messages=msg_list)
|
|
110
|
+
|
|
111
|
+
def inventory(self) -> pd.DataFrame:
|
|
112
|
+
"""Generate message inventory.
|
|
113
|
+
|
|
114
|
+
Returns a pandas DataFrame with one row per unique CAN ID, showing:
|
|
115
|
+
- arbitration_id: CAN ID
|
|
116
|
+
- count: Number of messages
|
|
117
|
+
- frequency_hz: Average frequency in Hz
|
|
118
|
+
- period_ms: Average period in milliseconds
|
|
119
|
+
- first_seen: Timestamp of first message
|
|
120
|
+
- last_seen: Timestamp of last message
|
|
121
|
+
- dlc: Data length (bytes)
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
DataFrame with message inventory.
|
|
125
|
+
"""
|
|
126
|
+
unique_ids = sorted(self._messages.unique_ids())
|
|
127
|
+
|
|
128
|
+
inventory_data = []
|
|
129
|
+
for arb_id in unique_ids:
|
|
130
|
+
filtered = self._messages.filter_by_id(arb_id)
|
|
131
|
+
timestamps = [msg.timestamp for msg in filtered.messages]
|
|
132
|
+
|
|
133
|
+
count = len(filtered)
|
|
134
|
+
first_seen = min(timestamps)
|
|
135
|
+
last_seen = max(timestamps)
|
|
136
|
+
duration = last_seen - first_seen
|
|
137
|
+
|
|
138
|
+
if duration > 0 and count > 1:
|
|
139
|
+
frequency_hz = (count - 1) / duration
|
|
140
|
+
period_ms = (duration / (count - 1)) * 1000
|
|
141
|
+
else:
|
|
142
|
+
frequency_hz = 0.0
|
|
143
|
+
period_ms = 0.0
|
|
144
|
+
|
|
145
|
+
# Get DLC from first message
|
|
146
|
+
dlc = filtered.messages[0].dlc
|
|
147
|
+
|
|
148
|
+
inventory_data.append(
|
|
149
|
+
{
|
|
150
|
+
"arbitration_id": f"0x{arb_id:03X}",
|
|
151
|
+
"count": count,
|
|
152
|
+
"frequency_hz": f"{frequency_hz:.1f}",
|
|
153
|
+
"period_ms": f"{period_ms:.1f}",
|
|
154
|
+
"first_seen": f"{first_seen:.6f}",
|
|
155
|
+
"last_seen": f"{last_seen:.6f}",
|
|
156
|
+
"dlc": dlc,
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return pd.DataFrame(inventory_data)
|
|
161
|
+
|
|
162
|
+
def message(self, arbitration_id: int) -> CANMessageWrapper:
|
|
163
|
+
"""Get message wrapper for analysis of a specific CAN ID.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
arbitration_id: CAN ID to analyze.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
CANMessageWrapper for this message ID.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ValueError: If no messages with this ID exist.
|
|
173
|
+
"""
|
|
174
|
+
from oscura.automotive.can.message_wrapper import CANMessageWrapper
|
|
175
|
+
|
|
176
|
+
filtered = self._messages.filter_by_id(arbitration_id)
|
|
177
|
+
if not filtered.messages:
|
|
178
|
+
raise ValueError(f"No messages found for ID 0x{arbitration_id:03X}")
|
|
179
|
+
|
|
180
|
+
return CANMessageWrapper(self, arbitration_id)
|
|
181
|
+
|
|
182
|
+
def analyze_message(self, arbitration_id: int, force_refresh: bool = False) -> MessageAnalysis:
|
|
183
|
+
"""Analyze a specific message ID.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
arbitration_id: CAN ID to analyze.
|
|
187
|
+
force_refresh: Force re-analysis even if cached.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
MessageAnalysis with complete analysis.
|
|
191
|
+
"""
|
|
192
|
+
# Check cache
|
|
193
|
+
if not force_refresh and arbitration_id in self._analyses_cache:
|
|
194
|
+
return self._analyses_cache[arbitration_id]
|
|
195
|
+
|
|
196
|
+
# Perform analysis
|
|
197
|
+
analysis = MessageAnalyzer.analyze_message_id(self._messages, arbitration_id)
|
|
198
|
+
|
|
199
|
+
# Cache result
|
|
200
|
+
self._analyses_cache[arbitration_id] = analysis
|
|
201
|
+
|
|
202
|
+
return analysis
|
|
203
|
+
|
|
204
|
+
def filter(
|
|
205
|
+
self,
|
|
206
|
+
min_frequency: float | None = None,
|
|
207
|
+
max_frequency: float | None = None,
|
|
208
|
+
arbitration_ids: list[int] | None = None,
|
|
209
|
+
time_range: tuple[float, float] | None = None,
|
|
210
|
+
) -> CANSession:
|
|
211
|
+
"""Filter messages and return new session.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
min_frequency: Minimum message frequency in Hz.
|
|
215
|
+
max_frequency: Maximum message frequency in Hz.
|
|
216
|
+
arbitration_ids: List of CAN IDs to include.
|
|
217
|
+
time_range: Tuple of (start_time, end_time) in seconds.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
New CANSession with filtered messages.
|
|
221
|
+
"""
|
|
222
|
+
filtered_messages = []
|
|
223
|
+
|
|
224
|
+
# First, filter by time range if specified
|
|
225
|
+
if time_range:
|
|
226
|
+
start_time, end_time = time_range
|
|
227
|
+
for msg in self._messages:
|
|
228
|
+
if start_time <= msg.timestamp <= end_time:
|
|
229
|
+
filtered_messages.append(msg)
|
|
230
|
+
else:
|
|
231
|
+
filtered_messages = list(self._messages)
|
|
232
|
+
|
|
233
|
+
# Filter by CAN IDs if specified
|
|
234
|
+
if arbitration_ids:
|
|
235
|
+
filtered_messages = [
|
|
236
|
+
msg for msg in filtered_messages if msg.arbitration_id in arbitration_ids
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
# Filter by frequency if specified
|
|
240
|
+
if min_frequency or max_frequency:
|
|
241
|
+
# Group by ID and calculate frequencies
|
|
242
|
+
from collections import defaultdict
|
|
243
|
+
|
|
244
|
+
id_messages: dict[int, list[CANMessage]] = defaultdict(list)
|
|
245
|
+
for msg in filtered_messages:
|
|
246
|
+
id_messages[msg.arbitration_id].append(msg)
|
|
247
|
+
|
|
248
|
+
# Filter IDs by frequency
|
|
249
|
+
valid_ids = set()
|
|
250
|
+
for arb_id, msgs in id_messages.items():
|
|
251
|
+
if len(msgs) > 1:
|
|
252
|
+
timestamps = [m.timestamp for m in msgs]
|
|
253
|
+
duration = max(timestamps) - min(timestamps)
|
|
254
|
+
if duration > 0:
|
|
255
|
+
freq = (len(msgs) - 1) / duration
|
|
256
|
+
|
|
257
|
+
if min_frequency and freq < min_frequency:
|
|
258
|
+
continue
|
|
259
|
+
if max_frequency and freq > max_frequency:
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
valid_ids.add(arb_id)
|
|
263
|
+
|
|
264
|
+
filtered_messages = [
|
|
265
|
+
msg for msg in filtered_messages if msg.arbitration_id in valid_ids
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
return CANSession.from_messages(filtered_messages)
|
|
269
|
+
|
|
270
|
+
def unique_ids(self) -> set[int]:
|
|
271
|
+
"""Get set of unique CAN IDs in this session.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Set of unique arbitration IDs.
|
|
275
|
+
"""
|
|
276
|
+
return self._messages.unique_ids()
|
|
277
|
+
|
|
278
|
+
def time_range(self) -> tuple[float, float]:
|
|
279
|
+
"""Get time range of all messages.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Tuple of (first_timestamp, last_timestamp).
|
|
283
|
+
"""
|
|
284
|
+
return self._messages.time_range()
|
|
285
|
+
|
|
286
|
+
def __len__(self) -> int:
|
|
287
|
+
"""Return total number of messages."""
|
|
288
|
+
return len(self._messages)
|
|
289
|
+
|
|
290
|
+
def compare_to(self, other_session: CANSession) -> StimulusResponseReport:
|
|
291
|
+
"""Compare this session to another to detect changes.
|
|
292
|
+
|
|
293
|
+
This method is useful for stimulus-response analysis where you compare
|
|
294
|
+
a baseline capture (no user action) against a stimulus capture (with
|
|
295
|
+
user action) to identify which messages and signals respond.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
other_session: Session to compare against (treated as stimulus).
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
StimulusResponseReport with detected changes.
|
|
302
|
+
|
|
303
|
+
Example - Brake pedal analysis:
|
|
304
|
+
>>> baseline = CANSession.from_log("no_brake.blf")
|
|
305
|
+
>>> stimulus = CANSession.from_log("brake_pressed.blf")
|
|
306
|
+
>>> report = baseline.compare_to(stimulus)
|
|
307
|
+
>>> print(report.summary())
|
|
308
|
+
>>> # Show which messages changed
|
|
309
|
+
>>> for msg_id in report.changed_messages:
|
|
310
|
+
... print(f"0x{msg_id:03X} responded to brake press")
|
|
311
|
+
|
|
312
|
+
Example - Throttle position analysis:
|
|
313
|
+
>>> idle = CANSession.from_log("idle.blf")
|
|
314
|
+
>>> throttle = CANSession.from_log("throttle_50pct.blf")
|
|
315
|
+
>>> report = idle.compare_to(throttle)
|
|
316
|
+
>>> # Examine byte-level changes
|
|
317
|
+
>>> for msg_id, changes in report.byte_changes.items():
|
|
318
|
+
... print(f"Message 0x{msg_id:03X}:")
|
|
319
|
+
... for change in changes:
|
|
320
|
+
... print(f" Byte {change.byte_position}: {change.change_magnitude:.2f}")
|
|
321
|
+
"""
|
|
322
|
+
from oscura.automotive.can.stimulus_response import (
|
|
323
|
+
StimulusResponseAnalyzer,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
analyzer = StimulusResponseAnalyzer()
|
|
327
|
+
return analyzer.detect_responses(self, other_session)
|
|
328
|
+
|
|
329
|
+
def find_message_pairs(
|
|
330
|
+
self,
|
|
331
|
+
time_window_ms: float = 100,
|
|
332
|
+
min_occurrence: int = 3,
|
|
333
|
+
) -> list[MessagePair]:
|
|
334
|
+
"""Find message pairs that frequently occur together.
|
|
335
|
+
|
|
336
|
+
Discovers request-response patterns and coordinated message transmissions
|
|
337
|
+
by detecting messages that consistently appear within a short time window.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
time_window_ms: Maximum time window in milliseconds.
|
|
341
|
+
min_occurrence: Minimum number of occurrences to report.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
List of MessagePair objects, sorted by occurrence count.
|
|
345
|
+
|
|
346
|
+
Example:
|
|
347
|
+
>>> session = CANSession.from_log("capture.blf")
|
|
348
|
+
>>> pairs = session.find_message_pairs(time_window_ms=50)
|
|
349
|
+
>>> for pair in pairs[:5]:
|
|
350
|
+
... print(pair)
|
|
351
|
+
"""
|
|
352
|
+
return PatternAnalyzer.find_message_pairs(
|
|
353
|
+
self, time_window_ms=time_window_ms, min_occurrence=min_occurrence
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def find_message_sequences(
|
|
357
|
+
self,
|
|
358
|
+
max_sequence_length: int = 5,
|
|
359
|
+
time_window_ms: float = 500,
|
|
360
|
+
min_support: float = 0.7,
|
|
361
|
+
) -> list[MessageSequence]:
|
|
362
|
+
"""Find message sequences (A → B → C patterns).
|
|
363
|
+
|
|
364
|
+
Discovers multi-step control sequences or protocol handshakes by
|
|
365
|
+
mining sequential patterns in the message stream.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
max_sequence_length: Maximum length of sequences (2-10).
|
|
369
|
+
time_window_ms: Maximum time window for entire sequence.
|
|
370
|
+
min_support: Minimum support score (0.0-1.0).
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
List of MessageSequence objects, sorted by support.
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
>>> session = CANSession.from_log("startup.blf")
|
|
377
|
+
>>> sequences = session.find_message_sequences(
|
|
378
|
+
... max_sequence_length=3,
|
|
379
|
+
... time_window_ms=1000
|
|
380
|
+
... )
|
|
381
|
+
>>> for seq in sequences[:5]:
|
|
382
|
+
... print(seq)
|
|
383
|
+
"""
|
|
384
|
+
return PatternAnalyzer.find_message_sequences(
|
|
385
|
+
self,
|
|
386
|
+
max_sequence_length=max_sequence_length,
|
|
387
|
+
time_window_ms=time_window_ms,
|
|
388
|
+
min_support=min_support,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def find_temporal_correlations(
|
|
392
|
+
self,
|
|
393
|
+
max_delay_ms: float = 100,
|
|
394
|
+
) -> dict[tuple[int, int], TemporalCorrelation]:
|
|
395
|
+
"""Find temporal correlations between messages.
|
|
396
|
+
|
|
397
|
+
Analyzes timing relationships to determine which messages consistently
|
|
398
|
+
follow others with predictable delays.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
max_delay_ms: Maximum delay to consider for correlations.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Dictionary mapping (leader_id, follower_id) to correlation info.
|
|
405
|
+
|
|
406
|
+
Example:
|
|
407
|
+
>>> session = CANSession.from_log("capture.blf")
|
|
408
|
+
>>> correlations = session.find_temporal_correlations(max_delay_ms=50)
|
|
409
|
+
>>> for (leader, follower), corr in correlations.items():
|
|
410
|
+
... print(f"0x{leader:03X} → 0x{follower:03X}: {corr.avg_delay_ms:.2f}ms")
|
|
411
|
+
"""
|
|
412
|
+
return PatternAnalyzer.find_temporal_correlations(self, max_delay_ms=max_delay_ms)
|
|
413
|
+
|
|
414
|
+
def learn_state_machine(
|
|
415
|
+
self, trigger_ids: list[int], context_window_ms: float = 500
|
|
416
|
+
) -> FiniteAutomaton:
|
|
417
|
+
"""Learn state machine from message sequences.
|
|
418
|
+
|
|
419
|
+
This method integrates TraceKit's state machine inference to learn
|
|
420
|
+
protocol state machines from CAN message sequences around trigger messages.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
trigger_ids: CAN IDs that trigger sequence extraction.
|
|
424
|
+
context_window_ms: Time window (ms) before trigger to capture sequences.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Learned finite automaton representing the state machine.
|
|
428
|
+
|
|
429
|
+
Example:
|
|
430
|
+
>>> session = CANSession.from_log("ignition_cycles.blf")
|
|
431
|
+
>>> automaton = session.learn_state_machine(
|
|
432
|
+
... trigger_ids=[0x280],
|
|
433
|
+
... context_window_ms=500
|
|
434
|
+
... )
|
|
435
|
+
>>> print(automaton.to_dot())
|
|
436
|
+
"""
|
|
437
|
+
from oscura.automotive.can.state_machine import learn_state_machine
|
|
438
|
+
|
|
439
|
+
return learn_state_machine(
|
|
440
|
+
session=self, trigger_ids=trigger_ids, context_window_ms=context_window_ms
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
def __repr__(self) -> str:
|
|
444
|
+
"""Human-readable representation."""
|
|
445
|
+
num_messages = len(self._messages)
|
|
446
|
+
num_ids = len(self.unique_ids())
|
|
447
|
+
time_start, time_end = self.time_range()
|
|
448
|
+
duration = time_end - time_start
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
f"CANSession({num_messages} messages, {num_ids} unique IDs, duration={duration:.2f}s)"
|
|
452
|
+
)
|