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
|
@@ -642,20 +642,33 @@ def recover_clock_fft(
|
|
|
642
642
|
Detects the dominant frequency component in the signal using
|
|
643
643
|
FFT analysis, suitable for periodic digital signals.
|
|
644
644
|
|
|
645
|
+
**Best for**: Long signals (>64 samples) with clear periodicity.
|
|
646
|
+
**Not recommended for**: Short random data, aperiodic signals.
|
|
647
|
+
For short signals, use recover_clock_edge() instead.
|
|
648
|
+
|
|
645
649
|
Args:
|
|
646
|
-
trace: Input trace (analog or digital).
|
|
650
|
+
trace: Input trace (analog or digital). Should have at least
|
|
651
|
+
4-5 cycles of the clock signal for reliable detection.
|
|
647
652
|
min_freq: Minimum frequency to consider (Hz). Default: sample_rate/1000.
|
|
648
653
|
max_freq: Maximum frequency to consider (Hz). Default: sample_rate/2.
|
|
649
654
|
|
|
650
655
|
Returns:
|
|
651
656
|
ClockRecoveryResult with recovered frequency and confidence.
|
|
657
|
+
Confidence < 0.5 indicates unreliable detection (warning issued).
|
|
652
658
|
|
|
653
659
|
Raises:
|
|
654
|
-
InsufficientDataError: If trace has fewer than
|
|
660
|
+
InsufficientDataError: If trace has fewer than 64 samples.
|
|
661
|
+
ValueError: If no frequency components found in specified range.
|
|
662
|
+
|
|
663
|
+
Warnings:
|
|
664
|
+
UserWarning: Issued when confidence < 0.5 (unreliable result).
|
|
655
665
|
|
|
656
666
|
Example:
|
|
657
667
|
>>> result = recover_clock_fft(trace)
|
|
658
|
-
>>>
|
|
668
|
+
>>> if result.confidence > 0.7:
|
|
669
|
+
... print(f"Clock: {result.frequency / 1e6:.3f} MHz")
|
|
670
|
+
>>> else:
|
|
671
|
+
... print("Low confidence - try edge-based recovery")
|
|
659
672
|
|
|
660
673
|
References:
|
|
661
674
|
IEEE 1241-2010 Section 4.1
|
|
@@ -665,12 +678,17 @@ def recover_clock_fft(
|
|
|
665
678
|
n = len(data)
|
|
666
679
|
sample_rate = trace.metadata.sample_rate
|
|
667
680
|
|
|
668
|
-
|
|
681
|
+
# FFT requires sufficient samples for reliable frequency resolution
|
|
682
|
+
# Rule of thumb: At least 4-5 cycles of the signal for accurate peak detection
|
|
683
|
+
# With typical bit rates, this means ~100-200 samples minimum
|
|
684
|
+
min_samples = 64 # Increased from 16 for better frequency resolution
|
|
685
|
+
if n < min_samples:
|
|
669
686
|
raise InsufficientDataError(
|
|
670
|
-
"FFT clock recovery requires at least
|
|
671
|
-
required=
|
|
687
|
+
f"FFT clock recovery requires at least {min_samples} samples for reliable frequency detection",
|
|
688
|
+
required=min_samples,
|
|
672
689
|
available=n,
|
|
673
690
|
analysis_type="clock_recovery_fft",
|
|
691
|
+
fix_hint="Use edge-based clock recovery for short signals or acquire more data",
|
|
674
692
|
)
|
|
675
693
|
|
|
676
694
|
# Set frequency range defaults
|
|
@@ -691,11 +709,11 @@ def recover_clock_fft(
|
|
|
691
709
|
valid_indices = np.where(mask)[0]
|
|
692
710
|
|
|
693
711
|
if len(valid_indices) == 0:
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
712
|
+
# No valid frequencies in range - signal may be DC or out of range
|
|
713
|
+
raise ValueError(
|
|
714
|
+
f"No frequency components found in range [{min_freq:.0f} Hz, {max_freq:.0f} Hz]. "
|
|
715
|
+
f"Signal may be constant (DC) or frequency is outside specified range. "
|
|
716
|
+
f"Adjust min_freq/max_freq or check signal integrity."
|
|
699
717
|
)
|
|
700
718
|
|
|
701
719
|
# Find peak in valid range
|
|
@@ -721,6 +739,18 @@ def recover_clock_fft(
|
|
|
721
739
|
|
|
722
740
|
period = 1.0 / peak_freq if peak_freq > 0 else np.nan
|
|
723
741
|
|
|
742
|
+
# Warn on low confidence results (may be unreliable)
|
|
743
|
+
if confidence < 0.5:
|
|
744
|
+
import warnings
|
|
745
|
+
|
|
746
|
+
warnings.warn(
|
|
747
|
+
f"FFT clock recovery has low confidence ({confidence:.2f}). "
|
|
748
|
+
f"Detected frequency: {peak_freq / 1e6:.3f} MHz. "
|
|
749
|
+
f"Consider using longer signal, edge-based recovery, or verifying signal periodicity.",
|
|
750
|
+
UserWarning,
|
|
751
|
+
stacklevel=2,
|
|
752
|
+
)
|
|
753
|
+
|
|
724
754
|
return ClockRecoveryResult(
|
|
725
755
|
frequency=float(peak_freq),
|
|
726
756
|
period=float(period),
|
|
@@ -6,8 +6,6 @@ This module provides payload extraction from PCAP packets with metadata
|
|
|
6
6
|
preservation, filtering, and multiple output formats.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
9
|
from collections.abc import Iterator, Sequence
|
|
12
10
|
from dataclasses import dataclass
|
|
13
11
|
from typing import Any, Literal
|
|
@@ -122,7 +120,7 @@ class PayloadExtractor:
|
|
|
122
120
|
packets: Sequence[dict[str, Any] | bytes],
|
|
123
121
|
protocol: str | None = None,
|
|
124
122
|
port_filter: tuple[int | None, int | None] | None = None,
|
|
125
|
-
) -> list[PayloadInfo]:
|
|
123
|
+
) -> list["PayloadInfo"]:
|
|
126
124
|
"""Extract payloads from all packets with metadata.
|
|
127
125
|
|
|
128
126
|
Implements RE-PAY-001: Batch payload extraction with metadata.
|
|
@@ -186,7 +184,7 @@ class PayloadExtractor:
|
|
|
186
184
|
def iter_payloads(
|
|
187
185
|
self,
|
|
188
186
|
packets: Sequence[dict[str, Any] | bytes],
|
|
189
|
-
) -> Iterator[PayloadInfo]:
|
|
187
|
+
) -> Iterator["PayloadInfo"]:
|
|
190
188
|
"""Iterate over payloads for memory-efficient processing.
|
|
191
189
|
|
|
192
190
|
Implements RE-PAY-001: Streaming payload iteration.
|
|
@@ -22,11 +22,11 @@ from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar
|
|
|
22
22
|
|
|
23
23
|
from oscura.analyzers.packet.parser import BinaryParser
|
|
24
24
|
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
|
|
25
27
|
if TYPE_CHECKING:
|
|
26
28
|
from collections.abc import Callable, Iterator
|
|
27
29
|
|
|
28
|
-
T = TypeVar("T")
|
|
29
|
-
|
|
30
30
|
|
|
31
31
|
@dataclass
|
|
32
32
|
class StreamPacket:
|
|
@@ -282,7 +282,7 @@ def pipeline(
|
|
|
282
282
|
yield from result
|
|
283
283
|
|
|
284
284
|
|
|
285
|
-
def batch
|
|
285
|
+
def batch(
|
|
286
286
|
source: Iterator[T],
|
|
287
287
|
size: int,
|
|
288
288
|
) -> Iterator[list[T]]:
|
|
@@ -311,7 +311,7 @@ def batch[T](
|
|
|
311
311
|
yield current_batch
|
|
312
312
|
|
|
313
313
|
|
|
314
|
-
def take
|
|
314
|
+
def take(source: Iterator[T], n: int) -> Iterator[T]:
|
|
315
315
|
"""Take first n items.
|
|
316
316
|
|
|
317
317
|
Args:
|
|
@@ -329,7 +329,7 @@ def take[T](source: Iterator[T], n: int) -> Iterator[T]:
|
|
|
329
329
|
count += 1 # noqa: SIM113
|
|
330
330
|
|
|
331
331
|
|
|
332
|
-
def skip
|
|
332
|
+
def skip(source: Iterator[T], n: int) -> Iterator[T]:
|
|
333
333
|
"""Skip first n items.
|
|
334
334
|
|
|
335
335
|
Args:
|
|
@@ -22,6 +22,8 @@ Author: Oscura Development Team
|
|
|
22
22
|
|
|
23
23
|
# Periodic pattern detection (PAT-001)
|
|
24
24
|
# Pattern clustering (PAT-004)
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
25
27
|
from .clustering import (
|
|
26
28
|
ClusteringResult,
|
|
27
29
|
ClusterResult,
|
|
@@ -125,7 +127,7 @@ def find_motifs(
|
|
|
125
127
|
return results
|
|
126
128
|
|
|
127
129
|
|
|
128
|
-
def extract_motif(data: Any, start: int, length: int) ->
|
|
130
|
+
def extract_motif(data: Any, start: int, length: int) -> NDArray[np.generic]:
|
|
129
131
|
"""Extract a motif from data.
|
|
130
132
|
|
|
131
133
|
Args:
|
|
@@ -137,10 +139,9 @@ def extract_motif(data: Any, start: int, length: int) -> "NDArray[np.generic]":
|
|
|
137
139
|
Extracted motif as numpy array.
|
|
138
140
|
"""
|
|
139
141
|
import numpy as np
|
|
140
|
-
from numpy.typing import NDArray
|
|
141
142
|
|
|
142
143
|
data_arr = np.asarray(data)
|
|
143
|
-
result
|
|
144
|
+
result = data_arr[start : start + length]
|
|
144
145
|
return result
|
|
145
146
|
|
|
146
147
|
|
|
@@ -7,6 +7,8 @@ using various distance metrics and clustering approaches.
|
|
|
7
7
|
Author: Oscura Development Team
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
10
12
|
from dataclasses import dataclass
|
|
11
13
|
from typing import Literal
|
|
12
14
|
|
|
@@ -984,7 +986,7 @@ class PatternClusterer:
|
|
|
984
986
|
|
|
985
987
|
def fit(
|
|
986
988
|
self, patterns: list[bytes | np.ndarray[tuple[int], np.dtype[np.uint8]]]
|
|
987
|
-
) ->
|
|
989
|
+
) -> PatternClusterer:
|
|
988
990
|
"""Fit the clusterer to patterns (sklearn-style interface).
|
|
989
991
|
|
|
990
992
|
Args:
|
oscura/analyzers/power/basic.py
CHANGED
oscura/analyzers/power/ripple.py
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Side-channel analysis module.
|
|
2
|
+
|
|
3
|
+
This module provides research-grade implementations of side-channel analysis
|
|
4
|
+
techniques including Differential Power Analysis (DPA), Correlation Power
|
|
5
|
+
Analysis (CPA), and timing analysis.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.side_channel import DPAAnalyzer, CPAAnalyzer
|
|
9
|
+
>>> # DPA attack
|
|
10
|
+
>>> dpa = DPAAnalyzer(target_bit=0)
|
|
11
|
+
>>> result = dpa.analyze(traces, plaintexts)
|
|
12
|
+
>>> print(f"Key byte guess: 0x{result.key_guess:02X}")
|
|
13
|
+
>>>
|
|
14
|
+
>>> # CPA attack
|
|
15
|
+
>>> cpa = CPAAnalyzer(leakage_model="hamming_weight")
|
|
16
|
+
>>> result = cpa.analyze(traces, plaintexts)
|
|
17
|
+
>>> print(f"Correlation: {result.max_correlation:.4f}")
|
|
18
|
+
|
|
19
|
+
References:
|
|
20
|
+
Kocher et al. "Differential Power Analysis" (CRYPTO 1999)
|
|
21
|
+
Brier et al. "Correlation Power Analysis" (CHES 2004)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from oscura.analyzers.side_channel.power import (
|
|
25
|
+
CPAAnalyzer,
|
|
26
|
+
CPAResult,
|
|
27
|
+
DPAAnalyzer,
|
|
28
|
+
DPAResult,
|
|
29
|
+
LeakageModel,
|
|
30
|
+
hamming_distance,
|
|
31
|
+
hamming_weight,
|
|
32
|
+
)
|
|
33
|
+
from oscura.analyzers.side_channel.timing import (
|
|
34
|
+
TimingAnalyzer,
|
|
35
|
+
TimingAttackResult,
|
|
36
|
+
TimingLeak,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Power analysis
|
|
41
|
+
"CPAAnalyzer",
|
|
42
|
+
"CPAResult",
|
|
43
|
+
"DPAAnalyzer",
|
|
44
|
+
"DPAResult",
|
|
45
|
+
"LeakageModel",
|
|
46
|
+
# Timing analysis
|
|
47
|
+
"TimingAnalyzer",
|
|
48
|
+
"TimingAttackResult",
|
|
49
|
+
"TimingLeak",
|
|
50
|
+
"hamming_distance",
|
|
51
|
+
"hamming_weight",
|
|
52
|
+
]
|