oscura 0.1.2__py3-none-any.whl → 0.4.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 +1 -7
- oscura/acquisition/__init__.py +147 -0
- oscura/acquisition/file.py +255 -0
- oscura/acquisition/hardware.py +186 -0
- oscura/acquisition/saleae.py +340 -0
- oscura/acquisition/socketcan.py +315 -0
- oscura/acquisition/streaming.py +38 -0
- oscura/acquisition/synthetic.py +229 -0
- oscura/acquisition/visa.py +376 -0
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/digital/clock.py +9 -1
- oscura/analyzers/digital/edges.py +1 -1
- oscura/analyzers/digital/timing.py +41 -11
- oscura/analyzers/packet/payload_extraction.py +2 -4
- oscura/analyzers/packet/stream.py +5 -5
- oscura/analyzers/patterns/__init__.py +4 -3
- oscura/analyzers/patterns/clustering.py +3 -1
- oscura/analyzers/power/ac_power.py +0 -2
- oscura/analyzers/power/basic.py +0 -2
- oscura/analyzers/power/ripple.py +0 -2
- oscura/analyzers/side_channel/__init__.py +52 -0
- oscura/analyzers/side_channel/power.py +690 -0
- oscura/analyzers/side_channel/timing.py +369 -0
- oscura/analyzers/signal_integrity/embedding.py +0 -2
- oscura/analyzers/signal_integrity/sparams.py +28 -206
- oscura/analyzers/spectral/fft.py +0 -2
- oscura/analyzers/statistical/__init__.py +3 -3
- oscura/analyzers/statistical/checksum.py +2 -0
- oscura/analyzers/statistical/classification.py +2 -0
- oscura/analyzers/statistical/entropy.py +11 -9
- oscura/analyzers/statistical/ngrams.py +4 -2
- oscura/api/fluent.py +2 -2
- oscura/automotive/__init__.py +4 -4
- oscura/automotive/can/__init__.py +0 -2
- oscura/automotive/can/patterns.py +3 -1
- oscura/automotive/can/session.py +277 -78
- oscura/automotive/can/state_machine.py +5 -2
- oscura/automotive/dbc/__init__.py +0 -2
- oscura/automotive/dtc/__init__.py +0 -2
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +37 -2769
- oscura/automotive/j1939/__init__.py +0 -2
- oscura/automotive/loaders/__init__.py +0 -2
- oscura/automotive/loaders/asc.py +0 -2
- oscura/automotive/loaders/blf.py +0 -2
- oscura/automotive/loaders/csv_can.py +0 -2
- oscura/automotive/obd/__init__.py +0 -2
- oscura/automotive/uds/__init__.py +0 -2
- oscura/automotive/uds/models.py +0 -2
- oscura/builders/__init__.py +9 -11
- oscura/builders/signal_builder.py +99 -191
- oscura/cli/main.py +0 -2
- oscura/cli/shell.py +0 -2
- oscura/config/loader.py +0 -2
- oscura/core/backend_selector.py +1 -1
- oscura/core/correlation.py +0 -2
- oscura/core/exceptions.py +61 -3
- oscura/core/lazy.py +5 -3
- oscura/core/memory_limits.py +0 -2
- oscura/core/numba_backend.py +5 -7
- oscura/core/uncertainty.py +3 -3
- oscura/dsl/interpreter.py +2 -0
- oscura/dsl/parser.py +8 -6
- oscura/exploratory/error_recovery.py +3 -3
- oscura/exploratory/parse.py +2 -0
- oscura/exploratory/recovery.py +2 -0
- oscura/exploratory/sync.py +2 -0
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/type_mapping.py +2 -0
- oscura/exporters/hdf5.py +1 -3
- oscura/extensibility/templates.py +0 -8
- oscura/inference/active_learning/lstar.py +2 -4
- oscura/inference/active_learning/observation_table.py +0 -2
- oscura/inference/active_learning/oracle.py +3 -1
- oscura/inference/active_learning/teachers/simulator.py +1 -3
- oscura/inference/alignment.py +2 -0
- oscura/inference/message_format.py +2 -0
- oscura/inference/protocol_dsl.py +7 -5
- oscura/inference/sequences.py +12 -14
- oscura/inference/state_machine.py +2 -0
- oscura/integrations/llm.py +3 -1
- oscura/jupyter/display.py +0 -2
- oscura/loaders/__init__.py +68 -51
- oscura/loaders/chipwhisperer.py +393 -0
- oscura/loaders/pcap.py +1 -1
- oscura/loaders/touchstone.py +221 -0
- oscura/math/arithmetic.py +0 -2
- oscura/optimization/parallel.py +9 -6
- oscura/pipeline/composition.py +0 -2
- oscura/plugins/cli.py +0 -2
- oscura/reporting/comparison.py +0 -2
- oscura/reporting/config.py +1 -1
- oscura/reporting/formatting/emphasis.py +2 -0
- oscura/reporting/formatting/numbers.py +0 -2
- oscura/reporting/output.py +1 -3
- oscura/reporting/sections.py +0 -2
- oscura/search/anomaly.py +2 -0
- oscura/session/session.py +91 -16
- oscura/sessions/__init__.py +70 -0
- oscura/sessions/base.py +323 -0
- oscura/sessions/blackbox.py +640 -0
- oscura/sessions/generic.py +189 -0
- oscura/testing/synthetic.py +2 -0
- oscura/ui/formatters.py +4 -2
- oscura/utils/buffer.py +2 -2
- oscura/utils/lazy.py +5 -5
- oscura/utils/memory_advanced.py +2 -2
- oscura/utils/memory_extensions.py +2 -2
- oscura/visualization/colors.py +0 -2
- oscura/visualization/power.py +2 -0
- oscura/workflows/multi_trace.py +2 -0
- {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/METADATA +122 -20
- {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/RECORD +116 -98
- {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/WHEEL +0 -0
- {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/licenses/LICENSE +0 -0
oscura/core/numba_backend.py
CHANGED
|
@@ -35,8 +35,6 @@ Example:
|
|
|
35
35
|
>>> result = sum_of_squares(data) # Fast on second call
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
|
-
from __future__ import annotations
|
|
39
|
-
|
|
40
38
|
import functools
|
|
41
39
|
from collections.abc import Callable
|
|
42
40
|
from typing import Any, TypeVar
|
|
@@ -45,11 +43,11 @@ import numpy as np
|
|
|
45
43
|
|
|
46
44
|
# Try to import Numba
|
|
47
45
|
try:
|
|
48
|
-
from numba import guvectorize as _numba_guvectorize # type: ignore[import-
|
|
49
|
-
from numba import jit as _numba_jit # type: ignore[import-
|
|
50
|
-
from numba import njit as _numba_njit # type: ignore[import-
|
|
51
|
-
from numba import prange as _numba_prange # type: ignore[import-
|
|
52
|
-
from numba import vectorize as _numba_vectorize # type: ignore[import-
|
|
46
|
+
from numba import guvectorize as _numba_guvectorize # type: ignore[import-not-found]
|
|
47
|
+
from numba import jit as _numba_jit # type: ignore[import-not-found]
|
|
48
|
+
from numba import njit as _numba_njit # type: ignore[import-not-found]
|
|
49
|
+
from numba import prange as _numba_prange # type: ignore[import-not-found]
|
|
50
|
+
from numba import vectorize as _numba_vectorize # type: ignore[import-not-found]
|
|
53
51
|
|
|
54
52
|
HAS_NUMBA = True
|
|
55
53
|
except ImportError:
|
oscura/core/uncertainty.py
CHANGED
|
@@ -108,7 +108,7 @@ class MeasurementWithUncertainty:
|
|
|
108
108
|
JCGM 100:2008 Section 5.1.6
|
|
109
109
|
"""
|
|
110
110
|
if self.value == 0:
|
|
111
|
-
return np.inf
|
|
111
|
+
return float(np.inf) # type: ignore[no-any-return]
|
|
112
112
|
return abs(self.uncertainty / self.value)
|
|
113
113
|
|
|
114
114
|
@property
|
|
@@ -173,7 +173,7 @@ class UncertaintyEstimator:
|
|
|
173
173
|
JCGM 100:2008 Section 4.2 (Type A evaluation)
|
|
174
174
|
"""
|
|
175
175
|
if len(data) < 2:
|
|
176
|
-
return np.nan
|
|
176
|
+
return float(np.nan) # type: ignore[no-any-return]
|
|
177
177
|
return float(np.std(data, ddof=1)) # Sample std (Bessel correction)
|
|
178
178
|
|
|
179
179
|
@staticmethod
|
|
@@ -190,7 +190,7 @@ class UncertaintyEstimator:
|
|
|
190
190
|
JCGM 100:2008 Section 4.2.3
|
|
191
191
|
"""
|
|
192
192
|
if len(data) < 2:
|
|
193
|
-
return np.nan
|
|
193
|
+
return float(np.nan) # type: ignore[no-any-return]
|
|
194
194
|
return float(np.std(data, ddof=1) / np.sqrt(len(data)))
|
|
195
195
|
|
|
196
196
|
@staticmethod
|
oscura/dsl/interpreter.py
CHANGED
oscura/dsl/parser.py
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Implements simple domain-specific language for trace analysis workflows.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
6
8
|
from dataclasses import dataclass
|
|
7
9
|
from enum import Enum, auto
|
|
8
10
|
from typing import Any, Union
|
|
@@ -352,14 +354,14 @@ class Assignment(ASTNode):
|
|
|
352
354
|
"""Variable assignment: $var = expr."""
|
|
353
355
|
|
|
354
356
|
variable: str
|
|
355
|
-
expression:
|
|
357
|
+
expression: Expression
|
|
356
358
|
|
|
357
359
|
|
|
358
360
|
@dataclass
|
|
359
361
|
class Pipeline(ASTNode):
|
|
360
362
|
"""Pipeline expression: expr | command | command."""
|
|
361
363
|
|
|
362
|
-
stages: list[
|
|
364
|
+
stages: list[Expression]
|
|
363
365
|
|
|
364
366
|
|
|
365
367
|
@dataclass
|
|
@@ -367,7 +369,7 @@ class Command(ASTNode):
|
|
|
367
369
|
"""Command invocation: command arg1 arg2."""
|
|
368
370
|
|
|
369
371
|
name: str
|
|
370
|
-
args: list[
|
|
372
|
+
args: list[Expression]
|
|
371
373
|
|
|
372
374
|
|
|
373
375
|
@dataclass
|
|
@@ -375,7 +377,7 @@ class FunctionCall(ASTNode):
|
|
|
375
377
|
"""Function call: func(arg1, arg2)."""
|
|
376
378
|
|
|
377
379
|
name: str
|
|
378
|
-
args: list[
|
|
380
|
+
args: list[Expression]
|
|
379
381
|
|
|
380
382
|
|
|
381
383
|
@dataclass
|
|
@@ -397,8 +399,8 @@ class ForLoop(ASTNode):
|
|
|
397
399
|
"""For loop: for $var in expr: body."""
|
|
398
400
|
|
|
399
401
|
variable: str
|
|
400
|
-
iterable:
|
|
401
|
-
body: list[
|
|
402
|
+
iterable: Expression
|
|
403
|
+
body: list[Statement]
|
|
402
404
|
|
|
403
405
|
|
|
404
406
|
# Type aliases
|
|
@@ -20,6 +20,8 @@ import numpy as np
|
|
|
20
20
|
|
|
21
21
|
from oscura.core.types import WaveformTrace
|
|
22
22
|
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
|
|
23
25
|
logger = logging.getLogger(__name__)
|
|
24
26
|
|
|
25
27
|
if TYPE_CHECKING:
|
|
@@ -27,8 +29,6 @@ if TYPE_CHECKING:
|
|
|
27
29
|
|
|
28
30
|
from numpy.typing import NDArray
|
|
29
31
|
|
|
30
|
-
T = TypeVar("T")
|
|
31
|
-
|
|
32
32
|
|
|
33
33
|
@dataclass
|
|
34
34
|
class RecoveryStats:
|
|
@@ -547,7 +547,7 @@ class RetryResult:
|
|
|
547
547
|
adjustments_made: list[str]
|
|
548
548
|
|
|
549
549
|
|
|
550
|
-
def retry_with_adjustment
|
|
550
|
+
def retry_with_adjustment(
|
|
551
551
|
func: Callable[..., T],
|
|
552
552
|
trace: WaveformTrace,
|
|
553
553
|
initial_params: dict[str, Any],
|
oscura/exploratory/parse.py
CHANGED
oscura/exploratory/recovery.py
CHANGED
oscura/exploratory/sync.py
CHANGED
|
@@ -106,7 +106,7 @@ class WiresharkDissectorGenerator:
|
|
|
106
106
|
|
|
107
107
|
# Render template
|
|
108
108
|
template = self.env.get_template("dissector.lua.j2")
|
|
109
|
-
return template.render(**context)
|
|
109
|
+
return str(template.render(**context)) # type: ignore[no-any-return]
|
|
110
110
|
|
|
111
111
|
def _build_template_context(self, protocol: ProtocolDefinition) -> dict[str, Any]:
|
|
112
112
|
"""Build context dictionary for template rendering.
|
oscura/exporters/hdf5.py
CHANGED
|
@@ -11,8 +11,6 @@ References:
|
|
|
11
11
|
HDF5 specification (https://www.hdfgroup.org/)
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
14
|
from datetime import datetime
|
|
17
15
|
from pathlib import Path
|
|
18
16
|
from typing import Any
|
|
@@ -81,7 +79,7 @@ def export_hdf5(
|
|
|
81
79
|
|
|
82
80
|
|
|
83
81
|
def _write_trace_dataset(
|
|
84
|
-
f: h5py.File,
|
|
82
|
+
f: "h5py.File",
|
|
85
83
|
name: str,
|
|
86
84
|
trace: WaveformTrace | DigitalTrace,
|
|
87
85
|
compression: str | None,
|
|
@@ -622,8 +622,6 @@ def _generate_analyzer_stub(template: PluginTemplate, class_name: str) -> str:
|
|
|
622
622
|
References:
|
|
623
623
|
PLUG-008: Plugin Template Generator
|
|
624
624
|
"""
|
|
625
|
-
from __future__ import annotations
|
|
626
|
-
|
|
627
625
|
import numpy as np
|
|
628
626
|
from numpy.typing import NDArray
|
|
629
627
|
|
|
@@ -697,8 +695,6 @@ def _generate_loader_stub(template: PluginTemplate, class_name: str) -> str:
|
|
|
697
695
|
References:
|
|
698
696
|
PLUG-008: Plugin Template Generator
|
|
699
697
|
"""
|
|
700
|
-
from __future__ import annotations
|
|
701
|
-
|
|
702
698
|
import numpy as np
|
|
703
699
|
from numpy.typing import NDArray
|
|
704
700
|
from pathlib import Path
|
|
@@ -782,8 +778,6 @@ def _generate_exporter_stub(template: PluginTemplate, class_name: str) -> str:
|
|
|
782
778
|
References:
|
|
783
779
|
PLUG-008: Plugin Template Generator
|
|
784
780
|
"""
|
|
785
|
-
from __future__ import annotations
|
|
786
|
-
|
|
787
781
|
import numpy as np
|
|
788
782
|
from numpy.typing import NDArray
|
|
789
783
|
from pathlib import Path
|
|
@@ -871,8 +865,6 @@ def _generate_generic_stub(template: PluginTemplate, class_name: str) -> str:
|
|
|
871
865
|
References:
|
|
872
866
|
PLUG-008: Plugin Template Generator
|
|
873
867
|
"""
|
|
874
|
-
from __future__ import annotations
|
|
875
|
-
|
|
876
868
|
|
|
877
869
|
class {class_name}:
|
|
878
870
|
"""Generic plugin implementation.
|
|
@@ -26,11 +26,9 @@ Complexity:
|
|
|
26
26
|
- Produces minimal DFA (fewest states)
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
|
-
from __future__ import annotations
|
|
30
|
-
|
|
31
29
|
from oscura.inference.active_learning.observation_table import ObservationTable
|
|
32
|
-
from oscura.inference.active_learning.oracle import Oracle
|
|
33
|
-
from oscura.inference.state_machine import FiniteAutomaton
|
|
30
|
+
from oscura.inference.active_learning.oracle import Oracle
|
|
31
|
+
from oscura.inference.state_machine import FiniteAutomaton
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
class LStarLearner:
|
|
@@ -11,8 +11,10 @@ References:
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
from abc import ABC, abstractmethod
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from oscura.inference.state_machine import FiniteAutomaton
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class Oracle(ABC):
|
|
@@ -7,10 +7,8 @@ requiring a live system.
|
|
|
7
7
|
The simulator teacher treats traces as examples of valid protocol sequences.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
10
|
from oscura.inference.active_learning.oracle import Oracle
|
|
13
|
-
from oscura.inference.state_machine import FiniteAutomaton
|
|
11
|
+
from oscura.inference.state_machine import FiniteAutomaton
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
class SimulatorTeacher(Oracle):
|
oscura/inference/alignment.py
CHANGED
|
@@ -18,6 +18,8 @@ References:
|
|
|
18
18
|
Discoverer: Automatic Protocol Reverse Engineering. USENIX Security 2007.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
21
23
|
from dataclasses import dataclass
|
|
22
24
|
from dataclasses import field as dataclass_field
|
|
23
25
|
from typing import Any, Literal
|
oscura/inference/protocol_dsl.py
CHANGED
|
@@ -13,6 +13,8 @@ Key capabilities:
|
|
|
13
13
|
- Comprehensive error reporting
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
16
18
|
import ast
|
|
17
19
|
import operator
|
|
18
20
|
import struct
|
|
@@ -68,7 +70,7 @@ class FieldDefinition:
|
|
|
68
70
|
element: dict[str, Any] | None = None # Element definition for arrays
|
|
69
71
|
count_field: str | None = None # Field containing array count
|
|
70
72
|
count: int | None = None # Fixed array count
|
|
71
|
-
fields: list[
|
|
73
|
+
fields: list[FieldDefinition] | None = None # Nested fields for struct type
|
|
72
74
|
|
|
73
75
|
def __post_init__(self) -> None:
|
|
74
76
|
"""Handle size_ref as alias for size."""
|
|
@@ -117,7 +119,7 @@ class ProtocolDefinition:
|
|
|
117
119
|
encoding: dict[str, Any] = field(default_factory=dict)
|
|
118
120
|
|
|
119
121
|
@classmethod
|
|
120
|
-
def from_yaml(cls, path: str | Path) ->
|
|
122
|
+
def from_yaml(cls, path: str | Path) -> ProtocolDefinition:
|
|
121
123
|
"""Load protocol definition from YAML file.
|
|
122
124
|
|
|
123
125
|
: YAML parsing.
|
|
@@ -134,7 +136,7 @@ class ProtocolDefinition:
|
|
|
134
136
|
return cls.from_dict(config)
|
|
135
137
|
|
|
136
138
|
@classmethod
|
|
137
|
-
def from_dict(cls, config: dict[str, Any]) ->
|
|
139
|
+
def from_dict(cls, config: dict[str, Any]) -> ProtocolDefinition:
|
|
138
140
|
"""Create from dictionary.
|
|
139
141
|
|
|
140
142
|
: Configuration parsing.
|
|
@@ -168,7 +170,7 @@ class ProtocolDefinition:
|
|
|
168
170
|
@classmethod
|
|
169
171
|
def _parse_field_definition(
|
|
170
172
|
cls, field_dict: dict[str, Any], default_endian: str
|
|
171
|
-
) ->
|
|
173
|
+
) -> FieldDefinition:
|
|
172
174
|
"""Parse a single field definition from dictionary.
|
|
173
175
|
|
|
174
176
|
Args:
|
|
@@ -365,7 +367,7 @@ class ProtocolDecoder:
|
|
|
365
367
|
self._endian_map: dict[str, str] = {"big": ">", "little": "<"}
|
|
366
368
|
|
|
367
369
|
@classmethod
|
|
368
|
-
def load(cls, path: str | Path) ->
|
|
370
|
+
def load(cls, path: str | Path) -> ProtocolDecoder:
|
|
369
371
|
"""Load decoder from YAML protocol definition.
|
|
370
372
|
|
|
371
373
|
: Load decoder from file.
|
oscura/inference/sequences.py
CHANGED
|
@@ -8,8 +8,6 @@ streams, identifying request-response pairs, and analyzing communication
|
|
|
8
8
|
flows.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
11
|
from collections import defaultdict
|
|
14
12
|
from collections.abc import Callable, Sequence
|
|
15
13
|
from dataclasses import dataclass, field
|
|
@@ -83,7 +81,7 @@ class CommunicationFlow:
|
|
|
83
81
|
|
|
84
82
|
flow_id: int
|
|
85
83
|
messages: list[Any]
|
|
86
|
-
pairs: list[RequestResponsePair]
|
|
84
|
+
pairs: list["RequestResponsePair"]
|
|
87
85
|
direction: str
|
|
88
86
|
participants: list[str]
|
|
89
87
|
duration: float
|
|
@@ -127,7 +125,7 @@ class SequencePatternDetector:
|
|
|
127
125
|
messages: Sequence[Any],
|
|
128
126
|
key: Callable[[Any], Any] | None = None,
|
|
129
127
|
timestamp_key: Callable[[Any], float] | None = None,
|
|
130
|
-
) -> list[SequencePattern]:
|
|
128
|
+
) -> list["SequencePattern"]:
|
|
131
129
|
"""Detect sequential patterns in message stream.
|
|
132
130
|
|
|
133
131
|
Implements RE-SEQ-002: Pattern detection workflow.
|
|
@@ -222,7 +220,7 @@ class SequencePatternDetector:
|
|
|
222
220
|
messages: Sequence[Any],
|
|
223
221
|
key: Callable[[Any], Any] | None = None,
|
|
224
222
|
timestamp_key: Callable[[Any], float] | None = None,
|
|
225
|
-
) -> list[SequencePattern]:
|
|
223
|
+
) -> list["SequencePattern"]:
|
|
226
224
|
"""Detect patterns that occur at regular intervals.
|
|
227
225
|
|
|
228
226
|
Implements RE-SEQ-002: Periodic pattern detection.
|
|
@@ -287,7 +285,7 @@ class SequencePatternDetector:
|
|
|
287
285
|
candidates: dict[tuple[Any, ...], list[int]],
|
|
288
286
|
identifiers: list[Any],
|
|
289
287
|
timestamps: list[float] | None,
|
|
290
|
-
) -> list[SequencePattern]:
|
|
288
|
+
) -> list["SequencePattern"]:
|
|
291
289
|
"""Score candidate patterns.
|
|
292
290
|
|
|
293
291
|
Args:
|
|
@@ -386,7 +384,7 @@ class RequestResponseCorrelator:
|
|
|
386
384
|
request_filter: Callable[[Any], bool] | None = None,
|
|
387
385
|
response_filter: Callable[[Any], bool] | None = None,
|
|
388
386
|
timestamp_key: Callable[[Any], float] | None = None,
|
|
389
|
-
) -> list[RequestResponsePair]:
|
|
387
|
+
) -> list["RequestResponsePair"]:
|
|
390
388
|
"""Correlate requests with responses.
|
|
391
389
|
|
|
392
390
|
Implements RE-SEQ-003: Request-response correlation workflow.
|
|
@@ -441,7 +439,7 @@ class RequestResponseCorrelator:
|
|
|
441
439
|
messages: Sequence[Any],
|
|
442
440
|
content_key: Callable[[Any], bytes],
|
|
443
441
|
timestamp_key: Callable[[Any], float] | None = None,
|
|
444
|
-
) -> list[RequestResponsePair]:
|
|
442
|
+
) -> list["RequestResponsePair"]:
|
|
445
443
|
"""Correlate by analyzing message content similarity.
|
|
446
444
|
|
|
447
445
|
Implements RE-SEQ-003: Content-based correlation.
|
|
@@ -500,10 +498,10 @@ class RequestResponseCorrelator:
|
|
|
500
498
|
|
|
501
499
|
def extract_flows(
|
|
502
500
|
self,
|
|
503
|
-
pairs: Sequence[RequestResponsePair],
|
|
501
|
+
pairs: Sequence["RequestResponsePair"],
|
|
504
502
|
messages: Sequence[Any],
|
|
505
503
|
flow_key: Callable[[Any], str] | None = None,
|
|
506
|
-
) -> list[CommunicationFlow]:
|
|
504
|
+
) -> list["CommunicationFlow"]:
|
|
507
505
|
"""Extract communication flows from pairs.
|
|
508
506
|
|
|
509
507
|
Implements RE-SEQ-003: Flow extraction.
|
|
@@ -569,7 +567,7 @@ class RequestResponseCorrelator:
|
|
|
569
567
|
self,
|
|
570
568
|
requests: list[tuple[int, Any, float, Any]],
|
|
571
569
|
responses: list[tuple[int, Any, float, Any]],
|
|
572
|
-
) -> list[RequestResponsePair]:
|
|
570
|
+
) -> list["RequestResponsePair"]:
|
|
573
571
|
"""Match request and response messages.
|
|
574
572
|
|
|
575
573
|
Args:
|
|
@@ -664,7 +662,7 @@ def detect_sequence_patterns(
|
|
|
664
662
|
min_length: int = 2,
|
|
665
663
|
max_length: int = 10,
|
|
666
664
|
min_frequency: int = 2,
|
|
667
|
-
) -> list[SequencePattern]:
|
|
665
|
+
) -> list["SequencePattern"]:
|
|
668
666
|
"""Detect sequential patterns in messages.
|
|
669
667
|
|
|
670
668
|
Implements RE-SEQ-002: Sequence Pattern Detection.
|
|
@@ -699,7 +697,7 @@ def correlate_requests(
|
|
|
699
697
|
response_filter: Callable[[Any], bool],
|
|
700
698
|
timestamp_key: Callable[[Any], float] | None = None,
|
|
701
699
|
max_latency: float = 10.0,
|
|
702
|
-
) -> list[RequestResponsePair]:
|
|
700
|
+
) -> list["RequestResponsePair"]:
|
|
703
701
|
"""Correlate request and response messages.
|
|
704
702
|
|
|
705
703
|
Implements RE-SEQ-003: Request-Response Correlation.
|
|
@@ -760,7 +758,7 @@ def find_message_dependencies(
|
|
|
760
758
|
|
|
761
759
|
|
|
762
760
|
def calculate_latency_stats(
|
|
763
|
-
pairs: Sequence[RequestResponsePair],
|
|
761
|
+
pairs: Sequence["RequestResponsePair"],
|
|
764
762
|
) -> dict[str, float]:
|
|
765
763
|
"""Calculate latency statistics for request-response pairs.
|
|
766
764
|
|
oscura/integrations/llm.py
CHANGED
|
@@ -23,6 +23,8 @@ Examples:
|
|
|
23
23
|
... )
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
26
28
|
import hashlib
|
|
27
29
|
import json
|
|
28
30
|
import os
|
|
@@ -1545,7 +1547,7 @@ def get_client_auto(**config_kwargs: Any) -> LLMClient:
|
|
|
1545
1547
|
|
|
1546
1548
|
def get_client_with_failover(
|
|
1547
1549
|
providers: list[str] | None = None, **config_kwargs: Any
|
|
1548
|
-
) ->
|
|
1550
|
+
) -> FailoverLLMClient:
|
|
1549
1551
|
"""Get LLM client with automatic failover between providers.
|
|
1550
1552
|
|
|
1551
1553
|
Failover logic (try OpenAI, fallback to Anthropic).
|
oscura/jupyter/display.py
CHANGED
oscura/loaders/__init__.py
CHANGED
|
@@ -20,10 +20,74 @@ from __future__ import annotations
|
|
|
20
20
|
import logging
|
|
21
21
|
import warnings
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
from typing import TYPE_CHECKING, Any
|
|
23
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
24
24
|
|
|
25
25
|
from oscura.core.exceptions import LoaderError, UnsupportedFormatError
|
|
26
|
-
from oscura.core.types import DigitalTrace, WaveformTrace
|
|
26
|
+
from oscura.core.types import DigitalTrace, IQTrace, WaveformTrace
|
|
27
|
+
|
|
28
|
+
# Loader registry for cleaner dispatch
|
|
29
|
+
_LOADER_REGISTRY: dict[str, tuple[str, str]] = {
|
|
30
|
+
"tektronix": ("oscura.loaders.tektronix", "load_tektronix_wfm"),
|
|
31
|
+
"tek": ("oscura.loaders.tektronix", "load_tektronix_wfm"),
|
|
32
|
+
"rigol": ("oscura.loaders.rigol", "load_rigol_wfm"),
|
|
33
|
+
"numpy": ("oscura.loaders.numpy_loader", "load_npz"),
|
|
34
|
+
"csv": ("oscura.loaders.csv_loader", "load_csv"),
|
|
35
|
+
"hdf5": ("oscura.loaders.hdf5_loader", "load_hdf5"),
|
|
36
|
+
"sigrok": ("oscura.loaders.sigrok", "load_sigrok"),
|
|
37
|
+
"vcd": ("oscura.loaders.vcd", "load_vcd"),
|
|
38
|
+
"pcap": ("oscura.loaders.pcap", "load_pcap"),
|
|
39
|
+
"wav": ("oscura.loaders.wav", "load_wav"),
|
|
40
|
+
"tdms": ("oscura.loaders.tdms", "load_tdms"),
|
|
41
|
+
"touchstone": ("oscura.loaders.touchstone", "load_touchstone"),
|
|
42
|
+
"chipwhisperer": ("oscura.loaders.chipwhisperer", "load_chipwhisperer"),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _dispatch_loader(
|
|
47
|
+
loader_name: str, path: Path, **kwargs: Any
|
|
48
|
+
) -> WaveformTrace | DigitalTrace | IQTrace:
|
|
49
|
+
"""Dispatch to registered loader.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
loader_name: Name of loader to use.
|
|
53
|
+
path: Path to file.
|
|
54
|
+
**kwargs: Additional arguments for loader.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Loaded data.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
UnsupportedFormatError: If loader not registered.
|
|
61
|
+
"""
|
|
62
|
+
if loader_name not in _LOADER_REGISTRY:
|
|
63
|
+
raise UnsupportedFormatError(
|
|
64
|
+
loader_name,
|
|
65
|
+
list(_LOADER_REGISTRY.keys()),
|
|
66
|
+
file_path=str(path),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
module_path, func_name = _LOADER_REGISTRY[loader_name]
|
|
70
|
+
|
|
71
|
+
# Dynamically import the module
|
|
72
|
+
import importlib
|
|
73
|
+
import inspect
|
|
74
|
+
|
|
75
|
+
module = importlib.import_module(module_path)
|
|
76
|
+
loader_func = getattr(module, func_name)
|
|
77
|
+
|
|
78
|
+
# Filter kwargs to only include parameters the function accepts
|
|
79
|
+
sig = inspect.signature(loader_func)
|
|
80
|
+
valid_kwargs = {}
|
|
81
|
+
for key, value in kwargs.items():
|
|
82
|
+
if key in sig.parameters or any(
|
|
83
|
+
p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
|
|
84
|
+
):
|
|
85
|
+
valid_kwargs[key] = value
|
|
86
|
+
|
|
87
|
+
# Call loader with appropriate arguments
|
|
88
|
+
result = loader_func(path, **valid_kwargs)
|
|
89
|
+
return cast("WaveformTrace | DigitalTrace | IQTrace", result)
|
|
90
|
+
|
|
27
91
|
|
|
28
92
|
# Import alias modules for DSL compatibility
|
|
29
93
|
from oscura.loaders import (
|
|
@@ -180,56 +244,9 @@ def load(
|
|
|
180
244
|
# Dispatch to appropriate loader
|
|
181
245
|
if loader_name == "auto_wfm":
|
|
182
246
|
return _load_wfm_auto(path, channel=channel, **kwargs)
|
|
183
|
-
elif loader_name in ("tektronix", "tek"):
|
|
184
|
-
from oscura.loaders.tektronix import load_tektronix_wfm
|
|
185
|
-
|
|
186
|
-
return load_tektronix_wfm(path, **kwargs)
|
|
187
|
-
elif loader_name == "rigol":
|
|
188
|
-
from oscura.loaders.rigol import load_rigol_wfm
|
|
189
|
-
|
|
190
|
-
return load_rigol_wfm(path, **kwargs)
|
|
191
|
-
elif loader_name == "numpy":
|
|
192
|
-
from oscura.loaders.numpy_loader import load_npz
|
|
193
|
-
|
|
194
|
-
return load_npz(path, channel=channel, **kwargs)
|
|
195
|
-
elif loader_name == "csv":
|
|
196
|
-
from oscura.loaders.csv_loader import load_csv
|
|
197
|
-
|
|
198
|
-
return load_csv(path, **kwargs) # type: ignore[return-value]
|
|
199
|
-
elif loader_name == "hdf5":
|
|
200
|
-
from oscura.loaders.hdf5_loader import load_hdf5
|
|
201
|
-
|
|
202
|
-
return load_hdf5(path, channel=channel, **kwargs) # type: ignore[return-value]
|
|
203
|
-
elif loader_name == "sigrok":
|
|
204
|
-
from oscura.loaders.sigrok import load_sigrok
|
|
205
|
-
|
|
206
|
-
return load_sigrok(path, channel=channel, **kwargs)
|
|
207
|
-
elif loader_name == "vcd":
|
|
208
|
-
from oscura.loaders.vcd import load_vcd
|
|
209
|
-
|
|
210
|
-
return load_vcd(path, **kwargs)
|
|
211
|
-
elif loader_name == "pcap":
|
|
212
|
-
from oscura.loaders.pcap import load_pcap
|
|
213
|
-
|
|
214
|
-
return load_pcap(path, **kwargs) # type: ignore[return-value]
|
|
215
|
-
elif loader_name == "wav":
|
|
216
|
-
from oscura.loaders.wav import load_wav
|
|
217
|
-
|
|
218
|
-
return load_wav(path, channel=channel, **kwargs)
|
|
219
|
-
elif loader_name == "tdms":
|
|
220
|
-
from oscura.loaders.tdms import load_tdms
|
|
221
|
-
|
|
222
|
-
return load_tdms(path, channel=channel, **kwargs)
|
|
223
|
-
elif loader_name == "touchstone":
|
|
224
|
-
from oscura.analyzers.signal_integrity.sparams import load_touchstone
|
|
225
|
-
|
|
226
|
-
return load_touchstone(path) # type: ignore[return-value]
|
|
227
247
|
else:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
list(SUPPORTED_FORMATS.keys()),
|
|
231
|
-
file_path=str(path),
|
|
232
|
-
)
|
|
248
|
+
# Use registry-based dispatch for all other loaders
|
|
249
|
+
return _dispatch_loader(loader_name, path, channel=channel, **kwargs)
|
|
233
250
|
|
|
234
251
|
|
|
235
252
|
def _load_wfm_auto(
|