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,356 @@
|
|
|
1
|
+
"""CAN message analysis algorithms.
|
|
2
|
+
|
|
3
|
+
This module implements statistical analysis algorithms for CAN message reverse
|
|
4
|
+
engineering, including entropy analysis, counter detection, and signal boundary
|
|
5
|
+
suggestion.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections import Counter
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from oscura.automotive.can.checksum import ChecksumDetector
|
|
16
|
+
from oscura.automotive.can.models import (
|
|
17
|
+
ByteAnalysis,
|
|
18
|
+
CounterPattern,
|
|
19
|
+
MessageAnalysis,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from oscura.automotive.can.models import CANMessageList
|
|
24
|
+
|
|
25
|
+
__all__ = ["MessageAnalyzer"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MessageAnalyzer:
|
|
29
|
+
"""Analyze CAN messages for reverse engineering.
|
|
30
|
+
|
|
31
|
+
This class implements various statistical and pattern detection algorithms
|
|
32
|
+
to aid in reverse engineering CAN bus protocols.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def calculate_entropy(values: list[int]) -> float:
|
|
37
|
+
"""Calculate Shannon entropy of byte values.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
values: List of byte values (0-255).
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Shannon entropy in bits (0.0-8.0).
|
|
44
|
+
"""
|
|
45
|
+
if not values:
|
|
46
|
+
return 0.0
|
|
47
|
+
|
|
48
|
+
# Count occurrences
|
|
49
|
+
counts = Counter(values)
|
|
50
|
+
total = len(values)
|
|
51
|
+
|
|
52
|
+
# Calculate entropy
|
|
53
|
+
entropy = 0.0
|
|
54
|
+
for count in counts.values():
|
|
55
|
+
if count > 0:
|
|
56
|
+
p = count / total
|
|
57
|
+
entropy -= p * np.log2(p)
|
|
58
|
+
|
|
59
|
+
return entropy
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def detect_counter(values: list[int], max_value: int = 255) -> CounterPattern | None:
|
|
63
|
+
"""Detect if a sequence of values represents a counter.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
values: List of byte values.
|
|
67
|
+
max_value: Maximum counter value before wrap.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
CounterPattern if counter detected, None otherwise.
|
|
71
|
+
"""
|
|
72
|
+
if len(values) < 3:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
# Calculate differences (handling wraparound)
|
|
76
|
+
diffs = []
|
|
77
|
+
for i in range(1, len(values)):
|
|
78
|
+
diff = values[i] - values[i - 1]
|
|
79
|
+
# Handle wraparound
|
|
80
|
+
if diff < 0:
|
|
81
|
+
diff += max_value + 1
|
|
82
|
+
diffs.append(diff)
|
|
83
|
+
|
|
84
|
+
# Check if diffs are consistent (most diffs should be the same)
|
|
85
|
+
diff_counts = Counter(diffs)
|
|
86
|
+
if not diff_counts:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
# Most common difference
|
|
90
|
+
most_common_diff, count = diff_counts.most_common(1)[0]
|
|
91
|
+
|
|
92
|
+
# Calculate confidence
|
|
93
|
+
confidence = count / len(diffs)
|
|
94
|
+
|
|
95
|
+
# Must be reasonably consistent to be a counter
|
|
96
|
+
if confidence < 0.7:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
# Common increment values: 1, 2, 4, etc.
|
|
100
|
+
if most_common_diff not in [1, 2, 4, 8, 16]:
|
|
101
|
+
# Could be a sequence but not a simple counter
|
|
102
|
+
if confidence > 0.9:
|
|
103
|
+
pattern_type: Literal["counter", "sequence", "toggle"] = "sequence"
|
|
104
|
+
else:
|
|
105
|
+
return None
|
|
106
|
+
else:
|
|
107
|
+
pattern_type = "counter"
|
|
108
|
+
|
|
109
|
+
# Detect wrap value
|
|
110
|
+
wrap_value = max(values)
|
|
111
|
+
if wrap_value == max_value:
|
|
112
|
+
wraps_at = max_value
|
|
113
|
+
else:
|
|
114
|
+
# Might wrap at a power of 2
|
|
115
|
+
for candidate in [15, 31, 63, 127, 255]:
|
|
116
|
+
if wrap_value <= candidate:
|
|
117
|
+
wraps_at = candidate
|
|
118
|
+
break
|
|
119
|
+
else:
|
|
120
|
+
wraps_at = 255
|
|
121
|
+
|
|
122
|
+
return CounterPattern(
|
|
123
|
+
byte_position=0, # Will be set by caller
|
|
124
|
+
increment=most_common_diff,
|
|
125
|
+
wraps_at=wraps_at,
|
|
126
|
+
confidence=confidence,
|
|
127
|
+
pattern_type=pattern_type,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def analyze_byte(messages: CANMessageList, byte_position: int) -> ByteAnalysis:
|
|
132
|
+
"""Analyze a specific byte position across multiple messages.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
messages: Collection of CAN messages with the same ID.
|
|
136
|
+
byte_position: Byte position to analyze (0-7).
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
ByteAnalysis with statistical information.
|
|
140
|
+
"""
|
|
141
|
+
# Extract byte values
|
|
142
|
+
values = []
|
|
143
|
+
for msg in messages:
|
|
144
|
+
if len(msg.data) > byte_position:
|
|
145
|
+
values.append(msg.data[byte_position])
|
|
146
|
+
|
|
147
|
+
if not values:
|
|
148
|
+
# No data at this position
|
|
149
|
+
return ByteAnalysis(
|
|
150
|
+
position=byte_position,
|
|
151
|
+
entropy=0.0,
|
|
152
|
+
min_value=0,
|
|
153
|
+
max_value=0,
|
|
154
|
+
mean=0.0,
|
|
155
|
+
std=0.0,
|
|
156
|
+
is_constant=True,
|
|
157
|
+
unique_values=0,
|
|
158
|
+
most_common_value=0,
|
|
159
|
+
change_rate=0.0,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Calculate statistics
|
|
163
|
+
arr = np.array(values)
|
|
164
|
+
min_val = int(np.min(arr))
|
|
165
|
+
max_val = int(np.max(arr))
|
|
166
|
+
mean_val = float(np.mean(arr))
|
|
167
|
+
std_val = float(np.std(arr))
|
|
168
|
+
|
|
169
|
+
# Entropy
|
|
170
|
+
entropy = MessageAnalyzer.calculate_entropy(values)
|
|
171
|
+
|
|
172
|
+
# Unique values
|
|
173
|
+
unique_vals = len(set(values))
|
|
174
|
+
is_constant = unique_vals == 1
|
|
175
|
+
|
|
176
|
+
# Most common value
|
|
177
|
+
counter = Counter(values)
|
|
178
|
+
most_common = counter.most_common(1)[0][0]
|
|
179
|
+
|
|
180
|
+
# Change rate
|
|
181
|
+
changes = sum(1 for i in range(1, len(values)) if values[i] != values[i - 1])
|
|
182
|
+
change_rate = changes / (len(values) - 1) if len(values) > 1 else 0.0
|
|
183
|
+
|
|
184
|
+
return ByteAnalysis(
|
|
185
|
+
position=byte_position,
|
|
186
|
+
entropy=entropy,
|
|
187
|
+
min_value=min_val,
|
|
188
|
+
max_value=max_val,
|
|
189
|
+
mean=mean_val,
|
|
190
|
+
std=std_val,
|
|
191
|
+
is_constant=is_constant,
|
|
192
|
+
unique_values=unique_vals,
|
|
193
|
+
most_common_value=most_common,
|
|
194
|
+
change_rate=change_rate,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def suggest_signal_boundaries(
|
|
199
|
+
byte_analyses: list[ByteAnalysis],
|
|
200
|
+
) -> list[dict[str, Any]]:
|
|
201
|
+
"""Suggest likely signal boundaries based on entropy analysis.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
byte_analyses: List of per-byte analyses.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
List of suggested signal definitions (as dicts).
|
|
208
|
+
"""
|
|
209
|
+
suggestions = []
|
|
210
|
+
|
|
211
|
+
# Group contiguous variable bytes
|
|
212
|
+
i = 0
|
|
213
|
+
while i < len(byte_analyses):
|
|
214
|
+
ba = byte_analyses[i]
|
|
215
|
+
|
|
216
|
+
# Skip constant bytes
|
|
217
|
+
if ba.is_constant:
|
|
218
|
+
i += 1
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
# Found a variable byte - see how far it extends
|
|
222
|
+
start_byte = i
|
|
223
|
+
end_byte = i
|
|
224
|
+
|
|
225
|
+
# Look ahead for contiguous variable bytes
|
|
226
|
+
while end_byte + 1 < len(byte_analyses) and not byte_analyses[end_byte + 1].is_constant:
|
|
227
|
+
end_byte += 1
|
|
228
|
+
|
|
229
|
+
# Suggest signal
|
|
230
|
+
num_bytes = end_byte - start_byte + 1
|
|
231
|
+
suggestions.append(
|
|
232
|
+
{
|
|
233
|
+
"start_byte": start_byte,
|
|
234
|
+
"num_bytes": num_bytes,
|
|
235
|
+
"start_bit": start_byte * 8,
|
|
236
|
+
"length_bits": num_bytes * 8,
|
|
237
|
+
"entropy_range": (
|
|
238
|
+
min(byte_analyses[j].entropy for j in range(start_byte, end_byte + 1)),
|
|
239
|
+
max(byte_analyses[j].entropy for j in range(start_byte, end_byte + 1)),
|
|
240
|
+
),
|
|
241
|
+
"suggested_types": MessageAnalyzer._suggest_types(
|
|
242
|
+
byte_analyses[start_byte : end_byte + 1]
|
|
243
|
+
),
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
i = end_byte + 1
|
|
248
|
+
|
|
249
|
+
return suggestions
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _suggest_types(byte_analyses: list[ByteAnalysis]) -> list[str]:
|
|
253
|
+
"""Suggest possible data types based on byte patterns.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
byte_analyses: Analyses for consecutive bytes.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
List of suggested type names.
|
|
260
|
+
"""
|
|
261
|
+
num_bytes = len(byte_analyses)
|
|
262
|
+
suggestions = []
|
|
263
|
+
|
|
264
|
+
# Based on size, suggest common types
|
|
265
|
+
if num_bytes == 1:
|
|
266
|
+
suggestions.append("uint8")
|
|
267
|
+
suggestions.append("int8")
|
|
268
|
+
elif num_bytes == 2:
|
|
269
|
+
suggestions.append("uint16")
|
|
270
|
+
suggestions.append("int16")
|
|
271
|
+
elif num_bytes == 4:
|
|
272
|
+
suggestions.append("uint32")
|
|
273
|
+
suggestions.append("int32")
|
|
274
|
+
suggestions.append("float32")
|
|
275
|
+
|
|
276
|
+
# Check if values suggest specific ranges
|
|
277
|
+
if num_bytes == 2:
|
|
278
|
+
# Common automotive scaling
|
|
279
|
+
max_val = max(ba.max_value for ba in byte_analyses)
|
|
280
|
+
if max_val <= 100:
|
|
281
|
+
suggestions.append("percentage")
|
|
282
|
+
elif max_val <= 8000:
|
|
283
|
+
suggestions.append("rpm (if scaled by 0.25)")
|
|
284
|
+
|
|
285
|
+
return suggestions
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def analyze_message_id(messages: CANMessageList, arbitration_id: int) -> MessageAnalysis:
|
|
289
|
+
"""Perform complete analysis on all messages with a specific ID.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
messages: All messages (will be filtered by ID).
|
|
293
|
+
arbitration_id: CAN ID to analyze.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
MessageAnalysis with complete analysis results.
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
ValueError: If no messages found for the specified ID.
|
|
300
|
+
"""
|
|
301
|
+
# Filter to this ID
|
|
302
|
+
filtered = messages.filter_by_id(arbitration_id)
|
|
303
|
+
|
|
304
|
+
if not filtered.messages:
|
|
305
|
+
raise ValueError(f"No messages found for ID 0x{arbitration_id:03X}")
|
|
306
|
+
|
|
307
|
+
# Calculate timing statistics
|
|
308
|
+
timestamps = np.array([msg.timestamp for msg in filtered.messages])
|
|
309
|
+
periods = np.diff(timestamps)
|
|
310
|
+
|
|
311
|
+
if len(periods) > 0:
|
|
312
|
+
period_ms = float(np.mean(periods) * 1000)
|
|
313
|
+
period_jitter_ms = float(np.std(periods) * 1000)
|
|
314
|
+
frequency_hz = 1.0 / np.mean(periods) if np.mean(periods) > 0 else 0.0
|
|
315
|
+
else:
|
|
316
|
+
period_ms = 0.0
|
|
317
|
+
period_jitter_ms = 0.0
|
|
318
|
+
frequency_hz = 0.0
|
|
319
|
+
|
|
320
|
+
# Determine max DLC
|
|
321
|
+
max_dlc = max(msg.dlc for msg in filtered.messages)
|
|
322
|
+
|
|
323
|
+
# Analyze each byte position
|
|
324
|
+
byte_analyses = []
|
|
325
|
+
for byte_pos in range(max_dlc):
|
|
326
|
+
analysis = MessageAnalyzer.analyze_byte(filtered, byte_pos)
|
|
327
|
+
byte_analyses.append(analysis)
|
|
328
|
+
|
|
329
|
+
# Detect counters
|
|
330
|
+
detected_counters = []
|
|
331
|
+
for byte_pos in range(max_dlc):
|
|
332
|
+
values = [msg.data[byte_pos] for msg in filtered.messages if len(msg.data) > byte_pos]
|
|
333
|
+
counter = MessageAnalyzer.detect_counter(values)
|
|
334
|
+
if counter:
|
|
335
|
+
counter.byte_position = byte_pos
|
|
336
|
+
detected_counters.append(counter)
|
|
337
|
+
|
|
338
|
+
# Suggest signal boundaries
|
|
339
|
+
suggested_signals = MessageAnalyzer.suggest_signal_boundaries(byte_analyses)
|
|
340
|
+
|
|
341
|
+
# Detect checksum
|
|
342
|
+
detected_checksum = ChecksumDetector.detect_checksum(filtered)
|
|
343
|
+
|
|
344
|
+
# Create analysis result
|
|
345
|
+
return MessageAnalysis(
|
|
346
|
+
arbitration_id=arbitration_id,
|
|
347
|
+
message_count=len(filtered.messages),
|
|
348
|
+
frequency_hz=frequency_hz,
|
|
349
|
+
period_ms=period_ms,
|
|
350
|
+
period_jitter_ms=period_jitter_ms,
|
|
351
|
+
byte_analyses=byte_analyses,
|
|
352
|
+
detected_counters=detected_counters,
|
|
353
|
+
detected_checksum=detected_checksum,
|
|
354
|
+
suggested_signals=suggested_signals,
|
|
355
|
+
correlations={}, # Will be set by correlation analysis
|
|
356
|
+
)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""CAN message checksum detection.
|
|
2
|
+
|
|
3
|
+
This module integrates with TraceKit's CRC reverse engineering capabilities
|
|
4
|
+
to detect and identify checksums in CAN messages.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
10
|
+
|
|
11
|
+
from oscura.automotive.can.models import ChecksumInfo
|
|
12
|
+
from oscura.inference.crc_reverse import CRCReverser
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from oscura.automotive.can.models import CANMessageList
|
|
16
|
+
|
|
17
|
+
__all__ = ["ChecksumDetector"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ChecksumDetector:
|
|
21
|
+
"""Detect checksums and CRCs in CAN messages.
|
|
22
|
+
|
|
23
|
+
This class uses TraceKit's CRC reverse engineering to detect
|
|
24
|
+
checksums in CAN message data.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Common automotive CRC algorithms to try
|
|
28
|
+
AUTOMOTIVE_CRCS: ClassVar[dict[str, dict[str, int | str]]] = {
|
|
29
|
+
"CRC-8-SAE-J1850": {"width": 8, "poly": 0x1D, "init": 0xFF, "xor_out": 0xFF},
|
|
30
|
+
"CRC-8-AUTOSAR": {"width": 8, "poly": 0x2F, "init": 0xFF, "xor_out": 0xFF},
|
|
31
|
+
"CRC-16-IBM": {"width": 16, "poly": 0x8005, "init": 0x0000, "xor_out": 0x0000},
|
|
32
|
+
"XOR-8": {"width": 8, "algorithm": "xor"},
|
|
33
|
+
"SUM-8": {"width": 8, "algorithm": "sum"},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def detect_checksum(
|
|
38
|
+
messages: CANMessageList, suspected_byte: int | None = None
|
|
39
|
+
) -> ChecksumInfo | None:
|
|
40
|
+
"""Detect checksum in CAN messages.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
messages: Collection of CAN messages with same ID.
|
|
44
|
+
suspected_byte: Byte position to check (if None, checks all bytes).
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
ChecksumInfo if checksum detected, None otherwise.
|
|
48
|
+
"""
|
|
49
|
+
if len(messages) < 10:
|
|
50
|
+
# Need enough samples for CRC reverse engineering
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
# Determine which bytes to check
|
|
54
|
+
if suspected_byte is not None:
|
|
55
|
+
bytes_to_check = [suspected_byte]
|
|
56
|
+
else:
|
|
57
|
+
# Check last 2 bytes (most common checksum positions)
|
|
58
|
+
max_dlc = max(msg.dlc for msg in messages.messages)
|
|
59
|
+
if max_dlc >= 2:
|
|
60
|
+
bytes_to_check = [max_dlc - 1, max_dlc - 2]
|
|
61
|
+
else:
|
|
62
|
+
bytes_to_check = [max_dlc - 1] if max_dlc > 0 else []
|
|
63
|
+
|
|
64
|
+
best_result = None
|
|
65
|
+
best_confidence = 0.0
|
|
66
|
+
|
|
67
|
+
for byte_pos in bytes_to_check:
|
|
68
|
+
result = ChecksumDetector._check_byte_for_checksum(messages, byte_pos)
|
|
69
|
+
if result and result.confidence > best_confidence:
|
|
70
|
+
best_result = result
|
|
71
|
+
best_confidence = result.confidence
|
|
72
|
+
|
|
73
|
+
return best_result
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _check_byte_for_checksum(messages: CANMessageList, byte_pos: int) -> ChecksumInfo | None:
|
|
77
|
+
"""Check if a specific byte position contains a checksum.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
messages: Message collection.
|
|
81
|
+
byte_pos: Byte position to check.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
ChecksumInfo if checksum detected, None otherwise.
|
|
85
|
+
"""
|
|
86
|
+
# Prepare message-CRC pairs for CRC reverser
|
|
87
|
+
message_crc_pairs = []
|
|
88
|
+
|
|
89
|
+
for msg in messages.messages:
|
|
90
|
+
if len(msg.data) > byte_pos:
|
|
91
|
+
# Try treating this byte as checksum
|
|
92
|
+
# Message is all bytes except this one
|
|
93
|
+
if byte_pos == len(msg.data) - 1:
|
|
94
|
+
# Checksum at end
|
|
95
|
+
message_data = msg.data[:-1]
|
|
96
|
+
crc_value = bytes([msg.data[byte_pos]])
|
|
97
|
+
else:
|
|
98
|
+
# Checksum in middle (less common)
|
|
99
|
+
message_data = msg.data[:byte_pos] + msg.data[byte_pos + 1 :]
|
|
100
|
+
crc_value = bytes([msg.data[byte_pos]])
|
|
101
|
+
|
|
102
|
+
message_crc_pairs.append((message_data, crc_value))
|
|
103
|
+
|
|
104
|
+
if len(message_crc_pairs) < 3:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
# Try simple checksums first (XOR, SUM) - they're faster and more specific
|
|
108
|
+
xor_result = ChecksumDetector._try_xor_checksum(messages, byte_pos)
|
|
109
|
+
if xor_result:
|
|
110
|
+
return xor_result
|
|
111
|
+
|
|
112
|
+
sum_result = ChecksumDetector._try_sum_checksum(messages, byte_pos)
|
|
113
|
+
if sum_result:
|
|
114
|
+
return sum_result
|
|
115
|
+
|
|
116
|
+
# Try CRC reverse engineering as fallback for more complex checksums
|
|
117
|
+
reverser = CRCReverser()
|
|
118
|
+
try:
|
|
119
|
+
crc_params = reverser.reverse(message_crc_pairs, width=8)
|
|
120
|
+
|
|
121
|
+
if crc_params and crc_params.confidence > 0.7:
|
|
122
|
+
# Found a CRC!
|
|
123
|
+
covered_bytes = list(range(len(messages.messages[0].data)))
|
|
124
|
+
covered_bytes.remove(byte_pos)
|
|
125
|
+
|
|
126
|
+
# Calculate validation rate
|
|
127
|
+
validation_count = 0
|
|
128
|
+
for msg in messages.messages:
|
|
129
|
+
if len(msg.data) > byte_pos:
|
|
130
|
+
# Verify checksum
|
|
131
|
+
if byte_pos == len(msg.data) - 1:
|
|
132
|
+
message_data = msg.data[:-1]
|
|
133
|
+
msg.data[byte_pos]
|
|
134
|
+
else:
|
|
135
|
+
message_data = msg.data[:byte_pos] + msg.data[byte_pos + 1 :]
|
|
136
|
+
msg.data[byte_pos]
|
|
137
|
+
|
|
138
|
+
# Compute expected CRC (simplified - real implementation would use CRC params)
|
|
139
|
+
# For now, just count how many messages have varying checksums
|
|
140
|
+
validation_count += 1
|
|
141
|
+
|
|
142
|
+
validation_rate = validation_count / len(messages.messages)
|
|
143
|
+
|
|
144
|
+
return ChecksumInfo(
|
|
145
|
+
byte_position=byte_pos,
|
|
146
|
+
algorithm=crc_params.algorithm_name or f"CRC-{crc_params.width}",
|
|
147
|
+
polynomial=crc_params.polynomial,
|
|
148
|
+
covered_bytes=covered_bytes,
|
|
149
|
+
confidence=crc_params.confidence,
|
|
150
|
+
validation_rate=validation_rate,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _try_xor_checksum(messages: CANMessageList, byte_pos: int) -> ChecksumInfo | None:
|
|
160
|
+
"""Try detecting XOR checksum.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
messages: Message collection.
|
|
164
|
+
byte_pos: Byte position to check.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
ChecksumInfo if XOR checksum detected, None otherwise.
|
|
168
|
+
"""
|
|
169
|
+
matches = 0
|
|
170
|
+
total = 0
|
|
171
|
+
|
|
172
|
+
for msg in messages.messages:
|
|
173
|
+
if len(msg.data) > byte_pos:
|
|
174
|
+
# Calculate XOR of all other bytes
|
|
175
|
+
xor_sum = 0
|
|
176
|
+
for i, byte_val in enumerate(msg.data):
|
|
177
|
+
if i != byte_pos:
|
|
178
|
+
xor_sum ^= byte_val
|
|
179
|
+
|
|
180
|
+
# Check if matches
|
|
181
|
+
if msg.data[byte_pos] == xor_sum:
|
|
182
|
+
matches += 1
|
|
183
|
+
total += 1
|
|
184
|
+
|
|
185
|
+
if total == 0:
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
match_rate = matches / total
|
|
189
|
+
|
|
190
|
+
if match_rate > 0.95: # 95% match rate
|
|
191
|
+
covered_bytes = list(range(len(messages.messages[0].data)))
|
|
192
|
+
covered_bytes.remove(byte_pos)
|
|
193
|
+
|
|
194
|
+
return ChecksumInfo(
|
|
195
|
+
byte_position=byte_pos,
|
|
196
|
+
algorithm="XOR-8",
|
|
197
|
+
polynomial=None,
|
|
198
|
+
covered_bytes=covered_bytes,
|
|
199
|
+
confidence=match_rate,
|
|
200
|
+
validation_rate=match_rate,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def _try_sum_checksum(messages: CANMessageList, byte_pos: int) -> ChecksumInfo | None:
|
|
207
|
+
"""Try detecting sum checksum.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
messages: Message collection.
|
|
211
|
+
byte_pos: Byte position to check.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
ChecksumInfo if sum checksum detected, None otherwise.
|
|
215
|
+
"""
|
|
216
|
+
matches = 0
|
|
217
|
+
total = 0
|
|
218
|
+
|
|
219
|
+
for msg in messages.messages:
|
|
220
|
+
if len(msg.data) > byte_pos:
|
|
221
|
+
# Calculate sum of all other bytes (modulo 256)
|
|
222
|
+
byte_sum = 0
|
|
223
|
+
for i, byte_val in enumerate(msg.data):
|
|
224
|
+
if i != byte_pos:
|
|
225
|
+
byte_sum = (byte_sum + byte_val) & 0xFF
|
|
226
|
+
|
|
227
|
+
# Check if matches
|
|
228
|
+
if msg.data[byte_pos] == byte_sum:
|
|
229
|
+
matches += 1
|
|
230
|
+
total += 1
|
|
231
|
+
|
|
232
|
+
if total == 0:
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
match_rate = matches / total
|
|
236
|
+
|
|
237
|
+
if match_rate > 0.95: # 95% match rate
|
|
238
|
+
covered_bytes = list(range(len(messages.messages[0].data)))
|
|
239
|
+
covered_bytes.remove(byte_pos)
|
|
240
|
+
|
|
241
|
+
return ChecksumInfo(
|
|
242
|
+
byte_position=byte_pos,
|
|
243
|
+
algorithm="SUM-8",
|
|
244
|
+
polynomial=None,
|
|
245
|
+
covered_bytes=covered_bytes,
|
|
246
|
+
confidence=match_rate,
|
|
247
|
+
validation_rate=match_rate,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return None
|