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.
Files changed (116) hide show
  1. oscura/__init__.py +1 -7
  2. oscura/acquisition/__init__.py +147 -0
  3. oscura/acquisition/file.py +255 -0
  4. oscura/acquisition/hardware.py +186 -0
  5. oscura/acquisition/saleae.py +340 -0
  6. oscura/acquisition/socketcan.py +315 -0
  7. oscura/acquisition/streaming.py +38 -0
  8. oscura/acquisition/synthetic.py +229 -0
  9. oscura/acquisition/visa.py +376 -0
  10. oscura/analyzers/__init__.py +3 -0
  11. oscura/analyzers/digital/clock.py +9 -1
  12. oscura/analyzers/digital/edges.py +1 -1
  13. oscura/analyzers/digital/timing.py +41 -11
  14. oscura/analyzers/packet/payload_extraction.py +2 -4
  15. oscura/analyzers/packet/stream.py +5 -5
  16. oscura/analyzers/patterns/__init__.py +4 -3
  17. oscura/analyzers/patterns/clustering.py +3 -1
  18. oscura/analyzers/power/ac_power.py +0 -2
  19. oscura/analyzers/power/basic.py +0 -2
  20. oscura/analyzers/power/ripple.py +0 -2
  21. oscura/analyzers/side_channel/__init__.py +52 -0
  22. oscura/analyzers/side_channel/power.py +690 -0
  23. oscura/analyzers/side_channel/timing.py +369 -0
  24. oscura/analyzers/signal_integrity/embedding.py +0 -2
  25. oscura/analyzers/signal_integrity/sparams.py +28 -206
  26. oscura/analyzers/spectral/fft.py +0 -2
  27. oscura/analyzers/statistical/__init__.py +3 -3
  28. oscura/analyzers/statistical/checksum.py +2 -0
  29. oscura/analyzers/statistical/classification.py +2 -0
  30. oscura/analyzers/statistical/entropy.py +11 -9
  31. oscura/analyzers/statistical/ngrams.py +4 -2
  32. oscura/api/fluent.py +2 -2
  33. oscura/automotive/__init__.py +4 -4
  34. oscura/automotive/can/__init__.py +0 -2
  35. oscura/automotive/can/patterns.py +3 -1
  36. oscura/automotive/can/session.py +277 -78
  37. oscura/automotive/can/state_machine.py +5 -2
  38. oscura/automotive/dbc/__init__.py +0 -2
  39. oscura/automotive/dtc/__init__.py +0 -2
  40. oscura/automotive/dtc/data.json +2763 -0
  41. oscura/automotive/dtc/database.py +37 -2769
  42. oscura/automotive/j1939/__init__.py +0 -2
  43. oscura/automotive/loaders/__init__.py +0 -2
  44. oscura/automotive/loaders/asc.py +0 -2
  45. oscura/automotive/loaders/blf.py +0 -2
  46. oscura/automotive/loaders/csv_can.py +0 -2
  47. oscura/automotive/obd/__init__.py +0 -2
  48. oscura/automotive/uds/__init__.py +0 -2
  49. oscura/automotive/uds/models.py +0 -2
  50. oscura/builders/__init__.py +9 -11
  51. oscura/builders/signal_builder.py +99 -191
  52. oscura/cli/main.py +0 -2
  53. oscura/cli/shell.py +0 -2
  54. oscura/config/loader.py +0 -2
  55. oscura/core/backend_selector.py +1 -1
  56. oscura/core/correlation.py +0 -2
  57. oscura/core/exceptions.py +61 -3
  58. oscura/core/lazy.py +5 -3
  59. oscura/core/memory_limits.py +0 -2
  60. oscura/core/numba_backend.py +5 -7
  61. oscura/core/uncertainty.py +3 -3
  62. oscura/dsl/interpreter.py +2 -0
  63. oscura/dsl/parser.py +8 -6
  64. oscura/exploratory/error_recovery.py +3 -3
  65. oscura/exploratory/parse.py +2 -0
  66. oscura/exploratory/recovery.py +2 -0
  67. oscura/exploratory/sync.py +2 -0
  68. oscura/export/wireshark/generator.py +1 -1
  69. oscura/export/wireshark/type_mapping.py +2 -0
  70. oscura/exporters/hdf5.py +1 -3
  71. oscura/extensibility/templates.py +0 -8
  72. oscura/inference/active_learning/lstar.py +2 -4
  73. oscura/inference/active_learning/observation_table.py +0 -2
  74. oscura/inference/active_learning/oracle.py +3 -1
  75. oscura/inference/active_learning/teachers/simulator.py +1 -3
  76. oscura/inference/alignment.py +2 -0
  77. oscura/inference/message_format.py +2 -0
  78. oscura/inference/protocol_dsl.py +7 -5
  79. oscura/inference/sequences.py +12 -14
  80. oscura/inference/state_machine.py +2 -0
  81. oscura/integrations/llm.py +3 -1
  82. oscura/jupyter/display.py +0 -2
  83. oscura/loaders/__init__.py +68 -51
  84. oscura/loaders/chipwhisperer.py +393 -0
  85. oscura/loaders/pcap.py +1 -1
  86. oscura/loaders/touchstone.py +221 -0
  87. oscura/math/arithmetic.py +0 -2
  88. oscura/optimization/parallel.py +9 -6
  89. oscura/pipeline/composition.py +0 -2
  90. oscura/plugins/cli.py +0 -2
  91. oscura/reporting/comparison.py +0 -2
  92. oscura/reporting/config.py +1 -1
  93. oscura/reporting/formatting/emphasis.py +2 -0
  94. oscura/reporting/formatting/numbers.py +0 -2
  95. oscura/reporting/output.py +1 -3
  96. oscura/reporting/sections.py +0 -2
  97. oscura/search/anomaly.py +2 -0
  98. oscura/session/session.py +91 -16
  99. oscura/sessions/__init__.py +70 -0
  100. oscura/sessions/base.py +323 -0
  101. oscura/sessions/blackbox.py +640 -0
  102. oscura/sessions/generic.py +189 -0
  103. oscura/testing/synthetic.py +2 -0
  104. oscura/ui/formatters.py +4 -2
  105. oscura/utils/buffer.py +2 -2
  106. oscura/utils/lazy.py +5 -5
  107. oscura/utils/memory_advanced.py +2 -2
  108. oscura/utils/memory_extensions.py +2 -2
  109. oscura/visualization/colors.py +0 -2
  110. oscura/visualization/power.py +2 -0
  111. oscura/workflows/multi_trace.py +2 -0
  112. {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/METADATA +122 -20
  113. {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/RECORD +116 -98
  114. {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/WHEEL +0 -0
  115. {oscura-0.1.2.dist-info → oscura-0.4.0.dist-info}/entry_points.txt +0 -0
  116. {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 16 samples.
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
- >>> print(f"Clock: {result.frequency / 1e6:.3f} MHz")
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
- if n < 16:
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 16 samples",
671
- required=16,
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
- return ClockRecoveryResult(
695
- frequency=np.nan,
696
- period=np.nan,
697
- method="fft",
698
- confidence=0.0,
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[T](
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[T](source: Iterator[T], n: int) -> Iterator[T]:
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[T](source: Iterator[T], n: int) -> Iterator[T]:
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) -> "NDArray[np.generic]":
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: NDArray[np.generic] = data_arr[start : start + length]
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
- ) -> "PatternClusterer":
989
+ ) -> PatternClusterer:
988
990
  """Fit the clusterer to patterns (sklearn-style interface).
989
991
 
990
992
  Args:
@@ -11,8 +11,6 @@ Example:
11
11
  >>> print(f"Power factor: {pf:.3f}, Reactive power: {q:.2f} VAR")
12
12
  """
13
13
 
14
- from __future__ import annotations
15
-
16
14
  import numpy as np
17
15
 
18
16
  from oscura.analyzers.power.basic import average_power
@@ -11,8 +11,6 @@ Example:
11
11
  >>> print(f"Average: {stats['average']:.2f} W, Peak: {stats['peak']:.2f} W")
12
12
  """
13
13
 
14
- from __future__ import annotations
15
-
16
14
  from typing import Any
17
15
 
18
16
  import numpy as np
@@ -9,8 +9,6 @@ Example:
9
9
  >>> print(f"Ripple: {r_pp*1e3:.2f} mV pp, {r_rms*1e3:.2f} mV rms")
10
10
  """
11
11
 
12
- from __future__ import annotations
13
-
14
12
  import numpy as np
15
13
  from scipy import signal
16
14
 
@@ -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
+ ]