oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
"""Sequence pattern detection and request-response correlation.
|
|
2
|
+
|
|
3
|
+
- RE-SEQ-002: Sequence Pattern Detection
|
|
4
|
+
- RE-SEQ-003: Request-Response Correlation
|
|
5
|
+
|
|
6
|
+
This module provides tools for detecting sequential patterns in message
|
|
7
|
+
streams, identifying request-response pairs, and analyzing communication
|
|
8
|
+
flows.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
from collections.abc import Callable, Sequence
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class SequencePattern:
|
|
23
|
+
"""A detected sequence pattern.
|
|
24
|
+
|
|
25
|
+
Implements RE-SEQ-002: Sequence pattern representation.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
pattern: List of message identifiers in sequence.
|
|
29
|
+
frequency: Number of occurrences.
|
|
30
|
+
positions: Starting positions in stream.
|
|
31
|
+
confidence: Detection confidence (0-1).
|
|
32
|
+
avg_gap: Average gap between elements.
|
|
33
|
+
gap_variance: Variance in inter-element gaps.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
pattern: list[Any]
|
|
37
|
+
frequency: int
|
|
38
|
+
positions: list[int] = field(default_factory=list)
|
|
39
|
+
confidence: float = 0.0
|
|
40
|
+
avg_gap: float = 0.0
|
|
41
|
+
gap_variance: float = 0.0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class RequestResponsePair:
|
|
46
|
+
"""A correlated request-response pair.
|
|
47
|
+
|
|
48
|
+
Implements RE-SEQ-003: Request-response pair.
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
request_index: Index of request message.
|
|
52
|
+
response_index: Index of response message.
|
|
53
|
+
request: Request message data.
|
|
54
|
+
response: Response message data.
|
|
55
|
+
latency: Time between request and response.
|
|
56
|
+
correlation_id: Detected correlation identifier.
|
|
57
|
+
confidence: Correlation confidence (0-1).
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
request_index: int
|
|
61
|
+
response_index: int
|
|
62
|
+
request: Any
|
|
63
|
+
response: Any
|
|
64
|
+
latency: float
|
|
65
|
+
correlation_id: bytes | int | None = None
|
|
66
|
+
confidence: float = 0.0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class CommunicationFlow:
|
|
71
|
+
"""A complete communication flow.
|
|
72
|
+
|
|
73
|
+
Implements RE-SEQ-003: Communication flow.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
flow_id: Unique flow identifier.
|
|
77
|
+
messages: List of messages in flow.
|
|
78
|
+
pairs: Request-response pairs.
|
|
79
|
+
direction: Primary direction ('request_first' or 'response_first').
|
|
80
|
+
participants: Identified participants.
|
|
81
|
+
duration: Total flow duration.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
flow_id: int
|
|
85
|
+
messages: list[Any]
|
|
86
|
+
pairs: list[RequestResponsePair]
|
|
87
|
+
direction: str
|
|
88
|
+
participants: list[str]
|
|
89
|
+
duration: float
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class SequencePatternDetector:
|
|
93
|
+
"""Detect sequential patterns in message streams.
|
|
94
|
+
|
|
95
|
+
Implements RE-SEQ-002: Sequence Pattern Detection.
|
|
96
|
+
|
|
97
|
+
Identifies recurring patterns of message types or values in
|
|
98
|
+
communication streams.
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> detector = SequencePatternDetector()
|
|
102
|
+
>>> patterns = detector.detect_patterns(messages, key=lambda m: m.type)
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
min_pattern_length: int = 2,
|
|
108
|
+
max_pattern_length: int = 10,
|
|
109
|
+
min_frequency: int = 2,
|
|
110
|
+
max_gap: float | None = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Initialize detector.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
min_pattern_length: Minimum pattern length.
|
|
116
|
+
max_pattern_length: Maximum pattern length.
|
|
117
|
+
min_frequency: Minimum occurrences to consider.
|
|
118
|
+
max_gap: Maximum allowed gap between pattern elements.
|
|
119
|
+
"""
|
|
120
|
+
self.min_pattern_length = min_pattern_length
|
|
121
|
+
self.max_pattern_length = max_pattern_length
|
|
122
|
+
self.min_frequency = min_frequency
|
|
123
|
+
self.max_gap = max_gap
|
|
124
|
+
|
|
125
|
+
def detect_patterns(
|
|
126
|
+
self,
|
|
127
|
+
messages: Sequence[Any],
|
|
128
|
+
key: Callable[[Any], Any] | None = None,
|
|
129
|
+
timestamp_key: Callable[[Any], float] | None = None,
|
|
130
|
+
) -> list[SequencePattern]:
|
|
131
|
+
"""Detect sequential patterns in message stream.
|
|
132
|
+
|
|
133
|
+
Implements RE-SEQ-002: Pattern detection workflow.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
messages: Sequence of messages.
|
|
137
|
+
key: Function to extract message identifier.
|
|
138
|
+
timestamp_key: Function to extract timestamp.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of detected patterns.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> patterns = detector.detect_patterns(
|
|
145
|
+
... messages,
|
|
146
|
+
... key=lambda m: m.get('type'),
|
|
147
|
+
... timestamp_key=lambda m: m.get('timestamp')
|
|
148
|
+
... )
|
|
149
|
+
"""
|
|
150
|
+
if not messages:
|
|
151
|
+
return []
|
|
152
|
+
|
|
153
|
+
# Extract identifiers
|
|
154
|
+
if key is not None:
|
|
155
|
+
identifiers = [key(m) for m in messages]
|
|
156
|
+
else:
|
|
157
|
+
identifiers = list(messages)
|
|
158
|
+
|
|
159
|
+
# Extract timestamps if provided
|
|
160
|
+
timestamps = None
|
|
161
|
+
if timestamp_key is not None:
|
|
162
|
+
timestamps = [timestamp_key(m) for m in messages]
|
|
163
|
+
|
|
164
|
+
# Find all n-grams
|
|
165
|
+
candidates = self._find_ngram_patterns(identifiers, timestamps)
|
|
166
|
+
|
|
167
|
+
# Filter and score
|
|
168
|
+
patterns = self._score_patterns(candidates, identifiers, timestamps)
|
|
169
|
+
|
|
170
|
+
# Sort by confidence
|
|
171
|
+
patterns.sort(key=lambda p: (-p.confidence, -p.frequency))
|
|
172
|
+
|
|
173
|
+
return patterns
|
|
174
|
+
|
|
175
|
+
def find_repeating_sequences(
|
|
176
|
+
self,
|
|
177
|
+
messages: Sequence[Any],
|
|
178
|
+
key: Callable[[Any], Any] | None = None,
|
|
179
|
+
) -> list[tuple[list[Any], int, list[int]]]:
|
|
180
|
+
"""Find exactly repeating message sequences.
|
|
181
|
+
|
|
182
|
+
Implements RE-SEQ-002: Exact sequence detection.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
messages: Sequence of messages.
|
|
186
|
+
key: Function to extract message identifier.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
List of (sequence, count, positions) tuples.
|
|
190
|
+
"""
|
|
191
|
+
if not messages:
|
|
192
|
+
return []
|
|
193
|
+
|
|
194
|
+
# Extract identifiers
|
|
195
|
+
if key is not None:
|
|
196
|
+
identifiers = tuple(key(m) for m in messages)
|
|
197
|
+
else:
|
|
198
|
+
identifiers = tuple(messages)
|
|
199
|
+
|
|
200
|
+
results = []
|
|
201
|
+
|
|
202
|
+
for length in range(self.min_pattern_length, self.max_pattern_length + 1):
|
|
203
|
+
# Count n-grams
|
|
204
|
+
ngram_positions = defaultdict(list)
|
|
205
|
+
|
|
206
|
+
for i in range(len(identifiers) - length + 1):
|
|
207
|
+
ngram = identifiers[i : i + length]
|
|
208
|
+
ngram_positions[ngram].append(i)
|
|
209
|
+
|
|
210
|
+
# Filter by frequency
|
|
211
|
+
for ngram, positions in ngram_positions.items():
|
|
212
|
+
if len(positions) >= self.min_frequency:
|
|
213
|
+
results.append((list(ngram), len(positions), positions))
|
|
214
|
+
|
|
215
|
+
# Sort by frequency
|
|
216
|
+
results.sort(key=lambda x: -x[1])
|
|
217
|
+
|
|
218
|
+
return results
|
|
219
|
+
|
|
220
|
+
def detect_periodic_patterns(
|
|
221
|
+
self,
|
|
222
|
+
messages: Sequence[Any],
|
|
223
|
+
key: Callable[[Any], Any] | None = None,
|
|
224
|
+
timestamp_key: Callable[[Any], float] | None = None,
|
|
225
|
+
) -> list[SequencePattern]:
|
|
226
|
+
"""Detect patterns that occur at regular intervals.
|
|
227
|
+
|
|
228
|
+
Implements RE-SEQ-002: Periodic pattern detection.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
messages: Sequence of messages.
|
|
232
|
+
key: Function to extract message identifier.
|
|
233
|
+
timestamp_key: Function to extract timestamp.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
List of periodic patterns.
|
|
237
|
+
"""
|
|
238
|
+
patterns = self.detect_patterns(messages, key, timestamp_key)
|
|
239
|
+
|
|
240
|
+
# Filter for low gap variance (periodic)
|
|
241
|
+
periodic = []
|
|
242
|
+
for pattern in patterns:
|
|
243
|
+
if pattern.frequency >= 3 and pattern.avg_gap > 0:
|
|
244
|
+
# Calculate coefficient of variation
|
|
245
|
+
if pattern.gap_variance > 0:
|
|
246
|
+
cv = (pattern.gap_variance**0.5) / pattern.avg_gap
|
|
247
|
+
else:
|
|
248
|
+
cv = 0
|
|
249
|
+
# Low CV indicates periodicity
|
|
250
|
+
if cv < 0.2: # Less than 20% variation
|
|
251
|
+
periodic.append(pattern)
|
|
252
|
+
|
|
253
|
+
return periodic
|
|
254
|
+
|
|
255
|
+
def _find_ngram_patterns(
|
|
256
|
+
self,
|
|
257
|
+
identifiers: list[Any],
|
|
258
|
+
timestamps: list[float] | None,
|
|
259
|
+
) -> dict[tuple[Any, ...], list[int]]:
|
|
260
|
+
"""Find all n-gram patterns.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
identifiers: Message identifiers.
|
|
264
|
+
timestamps: Message timestamps.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Dictionary mapping patterns to positions.
|
|
268
|
+
"""
|
|
269
|
+
candidates = defaultdict(list)
|
|
270
|
+
|
|
271
|
+
for length in range(self.min_pattern_length, self.max_pattern_length + 1):
|
|
272
|
+
for i in range(len(identifiers) - length + 1):
|
|
273
|
+
ngram = tuple(identifiers[i : i + length])
|
|
274
|
+
|
|
275
|
+
# Check gap constraint
|
|
276
|
+
if self.max_gap is not None and timestamps is not None:
|
|
277
|
+
gaps = [timestamps[i + j + 1] - timestamps[i + j] for j in range(length - 1)]
|
|
278
|
+
if any(g > self.max_gap for g in gaps):
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
candidates[ngram].append(i)
|
|
282
|
+
|
|
283
|
+
return candidates
|
|
284
|
+
|
|
285
|
+
def _score_patterns(
|
|
286
|
+
self,
|
|
287
|
+
candidates: dict[tuple[Any, ...], list[int]],
|
|
288
|
+
identifiers: list[Any],
|
|
289
|
+
timestamps: list[float] | None,
|
|
290
|
+
) -> list[SequencePattern]:
|
|
291
|
+
"""Score candidate patterns.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
candidates: Pattern -> positions mapping.
|
|
295
|
+
identifiers: Original identifiers.
|
|
296
|
+
timestamps: Message timestamps.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
List of scored patterns.
|
|
300
|
+
"""
|
|
301
|
+
patterns = []
|
|
302
|
+
|
|
303
|
+
for pattern_tuple, positions in candidates.items():
|
|
304
|
+
if len(positions) < self.min_frequency:
|
|
305
|
+
continue
|
|
306
|
+
|
|
307
|
+
pattern_list = list(pattern_tuple)
|
|
308
|
+
length = len(pattern_list)
|
|
309
|
+
|
|
310
|
+
# Calculate gap statistics if timestamps available
|
|
311
|
+
avg_gap = 0.0
|
|
312
|
+
gap_variance = 0.0
|
|
313
|
+
|
|
314
|
+
if timestamps is not None and len(positions) > 1:
|
|
315
|
+
# Calculate gaps between pattern occurrences
|
|
316
|
+
gaps = []
|
|
317
|
+
for i in range(len(positions) - 1):
|
|
318
|
+
start_time = timestamps[positions[i]]
|
|
319
|
+
end_time = timestamps[positions[i + 1]]
|
|
320
|
+
gaps.append(end_time - start_time)
|
|
321
|
+
|
|
322
|
+
if gaps:
|
|
323
|
+
avg_gap = sum(gaps) / len(gaps)
|
|
324
|
+
gap_variance = sum((g - avg_gap) ** 2 for g in gaps) / len(gaps)
|
|
325
|
+
|
|
326
|
+
# Calculate confidence
|
|
327
|
+
# Higher confidence for: frequent, consistent gaps, longer patterns
|
|
328
|
+
frequency_score = min(1.0, len(positions) / 10)
|
|
329
|
+
length_score = min(1.0, length / 5)
|
|
330
|
+
|
|
331
|
+
if gap_variance > 0 and avg_gap > 0:
|
|
332
|
+
consistency_score = 1.0 / (1.0 + (gap_variance**0.5 / avg_gap))
|
|
333
|
+
else:
|
|
334
|
+
consistency_score = 0.5
|
|
335
|
+
|
|
336
|
+
confidence = 0.4 * frequency_score + 0.3 * consistency_score + 0.3 * length_score
|
|
337
|
+
|
|
338
|
+
patterns.append(
|
|
339
|
+
SequencePattern(
|
|
340
|
+
pattern=pattern_list,
|
|
341
|
+
frequency=len(positions),
|
|
342
|
+
positions=positions,
|
|
343
|
+
confidence=confidence,
|
|
344
|
+
avg_gap=avg_gap,
|
|
345
|
+
gap_variance=gap_variance,
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return patterns
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class RequestResponseCorrelator:
|
|
353
|
+
"""Correlate request and response messages.
|
|
354
|
+
|
|
355
|
+
Implements RE-SEQ-003: Request-Response Correlation.
|
|
356
|
+
|
|
357
|
+
Identifies matching request-response pairs in bidirectional
|
|
358
|
+
communication streams.
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
>>> correlator = RequestResponseCorrelator()
|
|
362
|
+
>>> pairs = correlator.correlate(
|
|
363
|
+
... messages,
|
|
364
|
+
... request_filter=lambda m: m.type == 'REQ',
|
|
365
|
+
... response_filter=lambda m: m.type == 'RSP'
|
|
366
|
+
... )
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
def __init__(
|
|
370
|
+
self,
|
|
371
|
+
max_latency: float = 10.0,
|
|
372
|
+
correlation_key: Callable[[Any], Any] | None = None,
|
|
373
|
+
) -> None:
|
|
374
|
+
"""Initialize correlator.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
max_latency: Maximum time between request and response.
|
|
378
|
+
correlation_key: Function to extract correlation ID.
|
|
379
|
+
"""
|
|
380
|
+
self.max_latency = max_latency
|
|
381
|
+
self.correlation_key = correlation_key
|
|
382
|
+
|
|
383
|
+
def correlate(
|
|
384
|
+
self,
|
|
385
|
+
messages: Sequence[Any],
|
|
386
|
+
request_filter: Callable[[Any], bool] | None = None,
|
|
387
|
+
response_filter: Callable[[Any], bool] | None = None,
|
|
388
|
+
timestamp_key: Callable[[Any], float] | None = None,
|
|
389
|
+
) -> list[RequestResponsePair]:
|
|
390
|
+
"""Correlate requests with responses.
|
|
391
|
+
|
|
392
|
+
Implements RE-SEQ-003: Request-response correlation workflow.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
messages: All messages in stream.
|
|
396
|
+
request_filter: Function to identify requests.
|
|
397
|
+
response_filter: Function to identify responses.
|
|
398
|
+
timestamp_key: Function to extract timestamp.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
List of correlated pairs.
|
|
402
|
+
|
|
403
|
+
Example:
|
|
404
|
+
>>> pairs = correlator.correlate(
|
|
405
|
+
... messages,
|
|
406
|
+
... request_filter=lambda m: m['direction'] == 'out',
|
|
407
|
+
... response_filter=lambda m: m['direction'] == 'in',
|
|
408
|
+
... timestamp_key=lambda m: m['time']
|
|
409
|
+
... )
|
|
410
|
+
"""
|
|
411
|
+
# Separate requests and responses
|
|
412
|
+
requests = []
|
|
413
|
+
responses = []
|
|
414
|
+
|
|
415
|
+
for i, msg in enumerate(messages):
|
|
416
|
+
ts = timestamp_key(msg) if timestamp_key else float(i)
|
|
417
|
+
|
|
418
|
+
if request_filter is None or request_filter(msg):
|
|
419
|
+
correlation_id = None
|
|
420
|
+
if self.correlation_key is not None:
|
|
421
|
+
try:
|
|
422
|
+
correlation_id = self.correlation_key(msg)
|
|
423
|
+
except (KeyError, TypeError):
|
|
424
|
+
pass
|
|
425
|
+
requests.append((i, msg, ts, correlation_id))
|
|
426
|
+
|
|
427
|
+
if response_filter is None or response_filter(msg):
|
|
428
|
+
correlation_id = None
|
|
429
|
+
if self.correlation_key is not None:
|
|
430
|
+
try:
|
|
431
|
+
correlation_id = self.correlation_key(msg)
|
|
432
|
+
except (KeyError, TypeError):
|
|
433
|
+
pass
|
|
434
|
+
responses.append((i, msg, ts, correlation_id))
|
|
435
|
+
|
|
436
|
+
# Match pairs
|
|
437
|
+
return self._match_pairs(requests, responses)
|
|
438
|
+
|
|
439
|
+
def correlate_by_content(
|
|
440
|
+
self,
|
|
441
|
+
messages: Sequence[Any],
|
|
442
|
+
content_key: Callable[[Any], bytes],
|
|
443
|
+
timestamp_key: Callable[[Any], float] | None = None,
|
|
444
|
+
) -> list[RequestResponsePair]:
|
|
445
|
+
"""Correlate by analyzing message content similarity.
|
|
446
|
+
|
|
447
|
+
Implements RE-SEQ-003: Content-based correlation.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
messages: All messages.
|
|
451
|
+
content_key: Function to extract message content.
|
|
452
|
+
timestamp_key: Function to extract timestamp.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
List of correlated pairs.
|
|
456
|
+
"""
|
|
457
|
+
pairs = []
|
|
458
|
+
used_responses = set()
|
|
459
|
+
|
|
460
|
+
for i, msg in enumerate(messages):
|
|
461
|
+
req_content = content_key(msg)
|
|
462
|
+
req_ts = timestamp_key(msg) if timestamp_key else float(i)
|
|
463
|
+
|
|
464
|
+
best_match = None
|
|
465
|
+
best_score = 0.0
|
|
466
|
+
|
|
467
|
+
for j in range(i + 1, len(messages)):
|
|
468
|
+
if j in used_responses:
|
|
469
|
+
continue
|
|
470
|
+
|
|
471
|
+
resp = messages[j]
|
|
472
|
+
resp_ts = timestamp_key(resp) if timestamp_key else float(j)
|
|
473
|
+
|
|
474
|
+
latency = resp_ts - req_ts
|
|
475
|
+
if latency < 0 or latency > self.max_latency:
|
|
476
|
+
continue
|
|
477
|
+
|
|
478
|
+
resp_content = content_key(resp)
|
|
479
|
+
score = self._content_similarity(req_content, resp_content)
|
|
480
|
+
|
|
481
|
+
if score > best_score:
|
|
482
|
+
best_score = score
|
|
483
|
+
best_match = (j, resp, latency)
|
|
484
|
+
|
|
485
|
+
if best_match is not None and best_score > 0.3:
|
|
486
|
+
j, resp, latency = best_match
|
|
487
|
+
used_responses.add(j)
|
|
488
|
+
pairs.append(
|
|
489
|
+
RequestResponsePair(
|
|
490
|
+
request_index=i,
|
|
491
|
+
response_index=j,
|
|
492
|
+
request=msg,
|
|
493
|
+
response=resp,
|
|
494
|
+
latency=latency,
|
|
495
|
+
confidence=best_score,
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
return pairs
|
|
500
|
+
|
|
501
|
+
def extract_flows(
|
|
502
|
+
self,
|
|
503
|
+
pairs: Sequence[RequestResponsePair],
|
|
504
|
+
messages: Sequence[Any],
|
|
505
|
+
flow_key: Callable[[Any], str] | None = None,
|
|
506
|
+
) -> list[CommunicationFlow]:
|
|
507
|
+
"""Extract communication flows from pairs.
|
|
508
|
+
|
|
509
|
+
Implements RE-SEQ-003: Flow extraction.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
pairs: Correlated request-response pairs.
|
|
513
|
+
messages: All messages.
|
|
514
|
+
flow_key: Function to extract flow identifier.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
List of communication flows.
|
|
518
|
+
"""
|
|
519
|
+
if flow_key is None:
|
|
520
|
+
# Group all pairs into one flow
|
|
521
|
+
return [
|
|
522
|
+
CommunicationFlow(
|
|
523
|
+
flow_id=0,
|
|
524
|
+
messages=list(messages),
|
|
525
|
+
pairs=list(pairs),
|
|
526
|
+
direction="request_first",
|
|
527
|
+
participants=[],
|
|
528
|
+
duration=0.0,
|
|
529
|
+
)
|
|
530
|
+
]
|
|
531
|
+
|
|
532
|
+
# Group by flow key
|
|
533
|
+
flow_groups = defaultdict(list)
|
|
534
|
+
for pair in pairs:
|
|
535
|
+
key = flow_key(pair.request)
|
|
536
|
+
flow_groups[key].append(pair)
|
|
537
|
+
|
|
538
|
+
flows = []
|
|
539
|
+
for i, (key, group_pairs) in enumerate(flow_groups.items()):
|
|
540
|
+
# Get all messages in this flow
|
|
541
|
+
indices = set()
|
|
542
|
+
for pair in group_pairs:
|
|
543
|
+
indices.add(pair.request_index)
|
|
544
|
+
indices.add(pair.response_index)
|
|
545
|
+
|
|
546
|
+
flow_messages = [messages[j] for j in sorted(indices)]
|
|
547
|
+
|
|
548
|
+
# Calculate duration
|
|
549
|
+
if group_pairs:
|
|
550
|
+
_start = min(p.latency for p in group_pairs)
|
|
551
|
+
duration = max(p.latency for p in group_pairs)
|
|
552
|
+
else:
|
|
553
|
+
duration = 0.0
|
|
554
|
+
|
|
555
|
+
flows.append(
|
|
556
|
+
CommunicationFlow(
|
|
557
|
+
flow_id=i,
|
|
558
|
+
messages=flow_messages,
|
|
559
|
+
pairs=group_pairs,
|
|
560
|
+
direction="request_first",
|
|
561
|
+
participants=[str(key)],
|
|
562
|
+
duration=duration,
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
return flows
|
|
567
|
+
|
|
568
|
+
def _match_pairs(
|
|
569
|
+
self,
|
|
570
|
+
requests: list[tuple[int, Any, float, Any]],
|
|
571
|
+
responses: list[tuple[int, Any, float, Any]],
|
|
572
|
+
) -> list[RequestResponsePair]:
|
|
573
|
+
"""Match request and response messages.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
requests: List of (index, message, timestamp, correlation_id).
|
|
577
|
+
responses: List of (index, message, timestamp, correlation_id).
|
|
578
|
+
|
|
579
|
+
Returns:
|
|
580
|
+
List of matched pairs.
|
|
581
|
+
"""
|
|
582
|
+
pairs = []
|
|
583
|
+
used_responses = set()
|
|
584
|
+
|
|
585
|
+
for req_idx, req_msg, req_ts, req_id in requests:
|
|
586
|
+
best_match = None
|
|
587
|
+
best_score = 0.0
|
|
588
|
+
|
|
589
|
+
for resp_idx, resp_msg, resp_ts, resp_id in responses:
|
|
590
|
+
if resp_idx in used_responses:
|
|
591
|
+
continue
|
|
592
|
+
|
|
593
|
+
# Check timing
|
|
594
|
+
latency = resp_ts - req_ts
|
|
595
|
+
if latency < 0 or latency > self.max_latency:
|
|
596
|
+
continue
|
|
597
|
+
|
|
598
|
+
# Check correlation ID
|
|
599
|
+
if req_id is not None and resp_id is not None:
|
|
600
|
+
if req_id == resp_id:
|
|
601
|
+
score = 1.0
|
|
602
|
+
else:
|
|
603
|
+
score = 0.0
|
|
604
|
+
else:
|
|
605
|
+
# Use timing proximity
|
|
606
|
+
score = 1.0 - (latency / self.max_latency)
|
|
607
|
+
|
|
608
|
+
if score > best_score:
|
|
609
|
+
best_score = score
|
|
610
|
+
best_match = (resp_idx, resp_msg, latency, resp_id)
|
|
611
|
+
|
|
612
|
+
if best_match is not None:
|
|
613
|
+
resp_idx, resp_msg, latency, resp_id = best_match
|
|
614
|
+
used_responses.add(resp_idx)
|
|
615
|
+
|
|
616
|
+
pairs.append(
|
|
617
|
+
RequestResponsePair(
|
|
618
|
+
request_index=req_idx,
|
|
619
|
+
response_index=resp_idx,
|
|
620
|
+
request=req_msg,
|
|
621
|
+
response=resp_msg,
|
|
622
|
+
latency=latency,
|
|
623
|
+
correlation_id=req_id if req_id is not None else resp_id,
|
|
624
|
+
confidence=best_score,
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
return pairs
|
|
629
|
+
|
|
630
|
+
def _content_similarity(self, content_a: bytes, content_b: bytes) -> float:
|
|
631
|
+
"""Calculate content similarity.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
content_a: First content.
|
|
635
|
+
content_b: Second content.
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
Similarity score (0-1).
|
|
639
|
+
"""
|
|
640
|
+
if not content_a or not content_b:
|
|
641
|
+
return 0.0
|
|
642
|
+
|
|
643
|
+
# Use byte set similarity (Jaccard-like)
|
|
644
|
+
set_a = set(content_a)
|
|
645
|
+
set_b = set(content_b)
|
|
646
|
+
|
|
647
|
+
intersection = len(set_a & set_b)
|
|
648
|
+
union = len(set_a | set_b)
|
|
649
|
+
|
|
650
|
+
if union == 0:
|
|
651
|
+
return 0.0
|
|
652
|
+
|
|
653
|
+
return intersection / union
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
# =============================================================================
|
|
657
|
+
# Convenience functions
|
|
658
|
+
# =============================================================================
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def detect_sequence_patterns(
|
|
662
|
+
messages: Sequence[Any],
|
|
663
|
+
key: Callable[[Any], Any] | None = None,
|
|
664
|
+
min_length: int = 2,
|
|
665
|
+
max_length: int = 10,
|
|
666
|
+
min_frequency: int = 2,
|
|
667
|
+
) -> list[SequencePattern]:
|
|
668
|
+
"""Detect sequential patterns in messages.
|
|
669
|
+
|
|
670
|
+
Implements RE-SEQ-002: Sequence Pattern Detection.
|
|
671
|
+
|
|
672
|
+
Args:
|
|
673
|
+
messages: Sequence of messages.
|
|
674
|
+
key: Function to extract message identifier.
|
|
675
|
+
min_length: Minimum pattern length.
|
|
676
|
+
max_length: Maximum pattern length.
|
|
677
|
+
min_frequency: Minimum occurrences.
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
List of detected patterns.
|
|
681
|
+
|
|
682
|
+
Example:
|
|
683
|
+
>>> patterns = detect_sequence_patterns(
|
|
684
|
+
... messages,
|
|
685
|
+
... key=lambda m: m['type']
|
|
686
|
+
... )
|
|
687
|
+
"""
|
|
688
|
+
detector = SequencePatternDetector(
|
|
689
|
+
min_pattern_length=min_length,
|
|
690
|
+
max_pattern_length=max_length,
|
|
691
|
+
min_frequency=min_frequency,
|
|
692
|
+
)
|
|
693
|
+
return detector.detect_patterns(messages, key)
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def correlate_requests(
|
|
697
|
+
messages: Sequence[Any],
|
|
698
|
+
request_filter: Callable[[Any], bool],
|
|
699
|
+
response_filter: Callable[[Any], bool],
|
|
700
|
+
timestamp_key: Callable[[Any], float] | None = None,
|
|
701
|
+
max_latency: float = 10.0,
|
|
702
|
+
) -> list[RequestResponsePair]:
|
|
703
|
+
"""Correlate request and response messages.
|
|
704
|
+
|
|
705
|
+
Implements RE-SEQ-003: Request-Response Correlation.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
messages: All messages.
|
|
709
|
+
request_filter: Function to identify requests.
|
|
710
|
+
response_filter: Function to identify responses.
|
|
711
|
+
timestamp_key: Function to extract timestamp.
|
|
712
|
+
max_latency: Maximum time between request and response.
|
|
713
|
+
|
|
714
|
+
Returns:
|
|
715
|
+
List of correlated pairs.
|
|
716
|
+
|
|
717
|
+
Example:
|
|
718
|
+
>>> pairs = correlate_requests(
|
|
719
|
+
... messages,
|
|
720
|
+
... request_filter=lambda m: m['dir'] == 'out',
|
|
721
|
+
... response_filter=lambda m: m['dir'] == 'in',
|
|
722
|
+
... timestamp_key=lambda m: m['ts']
|
|
723
|
+
... )
|
|
724
|
+
"""
|
|
725
|
+
correlator = RequestResponseCorrelator(max_latency=max_latency)
|
|
726
|
+
return correlator.correlate(messages, request_filter, response_filter, timestamp_key)
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def find_message_dependencies(
|
|
730
|
+
messages: Sequence[Any],
|
|
731
|
+
key: Callable[[Any], Any],
|
|
732
|
+
timestamp_key: Callable[[Any], float] | None = None,
|
|
733
|
+
) -> dict[Any, list[Any]]:
|
|
734
|
+
"""Find dependencies between message types.
|
|
735
|
+
|
|
736
|
+
Implements RE-SEQ-002: Dependency detection.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
messages: Sequence of messages.
|
|
740
|
+
key: Function to extract message type.
|
|
741
|
+
timestamp_key: Function to extract timestamp.
|
|
742
|
+
|
|
743
|
+
Returns:
|
|
744
|
+
Dictionary mapping message types to their typical successors.
|
|
745
|
+
|
|
746
|
+
Example:
|
|
747
|
+
>>> deps = find_message_dependencies(messages, key=lambda m: m['type'])
|
|
748
|
+
>>> deps['REQ'] # ['RSP', 'ACK']
|
|
749
|
+
"""
|
|
750
|
+
dependencies: dict[Any, list[Any]] = defaultdict(list)
|
|
751
|
+
|
|
752
|
+
for i in range(len(messages) - 1):
|
|
753
|
+
current = key(messages[i])
|
|
754
|
+
next_msg = key(messages[i + 1])
|
|
755
|
+
|
|
756
|
+
if next_msg not in dependencies[current]:
|
|
757
|
+
dependencies[current].append(next_msg)
|
|
758
|
+
|
|
759
|
+
return dict(dependencies)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def calculate_latency_stats(
|
|
763
|
+
pairs: Sequence[RequestResponsePair],
|
|
764
|
+
) -> dict[str, float]:
|
|
765
|
+
"""Calculate latency statistics for request-response pairs.
|
|
766
|
+
|
|
767
|
+
Implements RE-SEQ-003: Latency analysis.
|
|
768
|
+
|
|
769
|
+
Args:
|
|
770
|
+
pairs: List of request-response pairs.
|
|
771
|
+
|
|
772
|
+
Returns:
|
|
773
|
+
Dictionary with min, max, mean, median, std latencies.
|
|
774
|
+
"""
|
|
775
|
+
if not pairs:
|
|
776
|
+
return {
|
|
777
|
+
"min": 0.0,
|
|
778
|
+
"max": 0.0,
|
|
779
|
+
"mean": 0.0,
|
|
780
|
+
"median": 0.0,
|
|
781
|
+
"std": 0.0,
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
latencies = [p.latency for p in pairs]
|
|
785
|
+
arr = np.array(latencies)
|
|
786
|
+
|
|
787
|
+
return {
|
|
788
|
+
"min": float(np.min(arr)),
|
|
789
|
+
"max": float(np.max(arr)),
|
|
790
|
+
"mean": float(np.mean(arr)),
|
|
791
|
+
"median": float(np.median(arr)),
|
|
792
|
+
"std": float(np.std(arr)),
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
__all__ = [
|
|
797
|
+
"CommunicationFlow",
|
|
798
|
+
"RequestResponseCorrelator",
|
|
799
|
+
"RequestResponsePair",
|
|
800
|
+
# Data classes
|
|
801
|
+
"SequencePattern",
|
|
802
|
+
# Classes
|
|
803
|
+
"SequencePatternDetector",
|
|
804
|
+
"calculate_latency_stats",
|
|
805
|
+
"correlate_requests",
|
|
806
|
+
# Functions
|
|
807
|
+
"detect_sequence_patterns",
|
|
808
|
+
"find_message_dependencies",
|
|
809
|
+
]
|