oscura 0.1.2__py3-none-any.whl → 0.3.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 (91) hide show
  1. oscura/__init__.py +1 -1
  2. oscura/analyzers/packet/payload_extraction.py +2 -4
  3. oscura/analyzers/packet/stream.py +5 -5
  4. oscura/analyzers/patterns/__init__.py +4 -3
  5. oscura/analyzers/patterns/clustering.py +3 -1
  6. oscura/analyzers/power/ac_power.py +0 -2
  7. oscura/analyzers/power/basic.py +0 -2
  8. oscura/analyzers/power/ripple.py +0 -2
  9. oscura/analyzers/signal_integrity/embedding.py +0 -2
  10. oscura/analyzers/signal_integrity/sparams.py +28 -206
  11. oscura/analyzers/spectral/fft.py +0 -2
  12. oscura/analyzers/statistical/__init__.py +3 -3
  13. oscura/analyzers/statistical/checksum.py +2 -0
  14. oscura/analyzers/statistical/classification.py +2 -0
  15. oscura/analyzers/statistical/entropy.py +11 -9
  16. oscura/analyzers/statistical/ngrams.py +4 -2
  17. oscura/api/fluent.py +2 -2
  18. oscura/automotive/__init__.py +1 -3
  19. oscura/automotive/can/__init__.py +0 -2
  20. oscura/automotive/dbc/__init__.py +0 -2
  21. oscura/automotive/dtc/__init__.py +0 -2
  22. oscura/automotive/dtc/data.json +2763 -0
  23. oscura/automotive/dtc/database.py +37 -2769
  24. oscura/automotive/j1939/__init__.py +0 -2
  25. oscura/automotive/loaders/__init__.py +0 -2
  26. oscura/automotive/loaders/asc.py +0 -2
  27. oscura/automotive/loaders/blf.py +0 -2
  28. oscura/automotive/loaders/csv_can.py +0 -2
  29. oscura/automotive/obd/__init__.py +0 -2
  30. oscura/automotive/uds/__init__.py +0 -2
  31. oscura/automotive/uds/models.py +0 -2
  32. oscura/cli/main.py +0 -2
  33. oscura/cli/shell.py +0 -2
  34. oscura/config/loader.py +0 -2
  35. oscura/core/backend_selector.py +1 -1
  36. oscura/core/correlation.py +0 -2
  37. oscura/core/exceptions.py +56 -2
  38. oscura/core/lazy.py +5 -3
  39. oscura/core/memory_limits.py +0 -2
  40. oscura/core/numba_backend.py +5 -7
  41. oscura/core/uncertainty.py +3 -3
  42. oscura/dsl/interpreter.py +2 -0
  43. oscura/dsl/parser.py +8 -6
  44. oscura/exploratory/error_recovery.py +3 -3
  45. oscura/exploratory/parse.py +2 -0
  46. oscura/exploratory/recovery.py +2 -0
  47. oscura/exploratory/sync.py +2 -0
  48. oscura/export/wireshark/generator.py +1 -1
  49. oscura/export/wireshark/type_mapping.py +2 -0
  50. oscura/exporters/hdf5.py +1 -3
  51. oscura/extensibility/templates.py +0 -8
  52. oscura/inference/active_learning/lstar.py +2 -4
  53. oscura/inference/active_learning/observation_table.py +0 -2
  54. oscura/inference/active_learning/oracle.py +3 -1
  55. oscura/inference/active_learning/teachers/simulator.py +1 -3
  56. oscura/inference/alignment.py +2 -0
  57. oscura/inference/message_format.py +2 -0
  58. oscura/inference/protocol_dsl.py +7 -5
  59. oscura/inference/sequences.py +12 -14
  60. oscura/inference/state_machine.py +2 -0
  61. oscura/integrations/llm.py +3 -1
  62. oscura/jupyter/display.py +0 -2
  63. oscura/loaders/__init__.py +67 -51
  64. oscura/loaders/pcap.py +1 -1
  65. oscura/loaders/touchstone.py +221 -0
  66. oscura/math/arithmetic.py +0 -2
  67. oscura/optimization/parallel.py +9 -6
  68. oscura/pipeline/composition.py +0 -2
  69. oscura/plugins/cli.py +0 -2
  70. oscura/reporting/comparison.py +0 -2
  71. oscura/reporting/config.py +1 -1
  72. oscura/reporting/formatting/emphasis.py +2 -0
  73. oscura/reporting/formatting/numbers.py +0 -2
  74. oscura/reporting/output.py +1 -3
  75. oscura/reporting/sections.py +0 -2
  76. oscura/search/anomaly.py +2 -0
  77. oscura/session/session.py +80 -13
  78. oscura/testing/synthetic.py +2 -0
  79. oscura/ui/formatters.py +4 -2
  80. oscura/utils/buffer.py +2 -2
  81. oscura/utils/lazy.py +5 -5
  82. oscura/utils/memory_advanced.py +2 -2
  83. oscura/utils/memory_extensions.py +2 -2
  84. oscura/visualization/colors.py +0 -2
  85. oscura/visualization/power.py +2 -0
  86. oscura/workflows/multi_trace.py +2 -0
  87. {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/METADATA +37 -16
  88. {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/RECORD +91 -89
  89. {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/WHEEL +0 -0
  90. {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/entry_points.txt +0 -0
  91. {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/licenses/LICENSE +0 -0
oscura/session/session.py CHANGED
@@ -15,17 +15,26 @@ Example:
15
15
  from __future__ import annotations
16
16
 
17
17
  import gzip
18
+ import hashlib
19
+ import hmac
18
20
  import pickle
21
+ import warnings
19
22
  from dataclasses import dataclass, field
20
23
  from datetime import datetime
21
24
  from pathlib import Path
22
- from typing import Any
25
+ from typing import Any, cast
23
26
 
24
27
  import numpy as np
25
28
 
29
+ from oscura.core.exceptions import SecurityError
26
30
  from oscura.session.annotations import AnnotationLayer
27
31
  from oscura.session.history import OperationHistory
28
32
 
33
+ # Session file format constants
34
+ _SESSION_MAGIC = b"OSC1" # Magic bytes for new format with signature
35
+ _SESSION_SIGNATURE_SIZE = 32 # SHA256 hash size in bytes
36
+ _SECURITY_KEY = hashlib.sha256(b"oscura-session-v1").digest()
37
+
29
38
 
30
39
  @dataclass
31
40
  class Session:
@@ -273,7 +282,7 @@ class Session:
273
282
  include_traces: bool = True,
274
283
  compress: bool = True,
275
284
  ) -> Path:
276
- """Save session to file.
285
+ """Save session to file with HMAC signature for integrity verification.
277
286
 
278
287
  Args:
279
288
  path: Output path (default: use existing or generate).
@@ -287,9 +296,10 @@ class Session:
287
296
  >>> session.save('analysis.tks')
288
297
 
289
298
  Security Note:
290
- Session files use pickle serialization for flexibility. Share
291
- session files only with trusted parties. For secure data exchange
292
- with untrusted parties, use JSON or HDF5 export formats instead.
299
+ Session files now include HMAC signatures for integrity verification.
300
+ Files are still pickle-based - only load from trusted sources.
301
+ For secure data exchange with untrusted parties, use JSON or HDF5
302
+ export formats instead.
293
303
  """
294
304
  if path is None:
295
305
  path = self._file_path or Path(f"{self.name.replace(' ', '_')}.tks")
@@ -302,13 +312,23 @@ class Session:
302
312
  # Build session data
303
313
  data = self._to_dict(include_traces=include_traces)
304
314
 
305
- # Serialize
315
+ # Serialize with pickle
316
+ serialized = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
317
+
318
+ # Compute HMAC signature
319
+ signature = hmac.new(_SECURITY_KEY, serialized, hashlib.sha256).digest()
320
+
321
+ # Write: magic bytes + signature + pickled data
306
322
  if compress:
307
323
  with gzip.open(path, "wb") as f:
308
- pickle.dump(data, f)
324
+ f.write(_SESSION_MAGIC)
325
+ f.write(signature)
326
+ f.write(serialized)
309
327
  else:
310
328
  with open(path, "wb") as f:
311
- pickle.dump(data, f)
329
+ f.write(_SESSION_MAGIC)
330
+ f.write(signature)
331
+ f.write(serialized)
312
332
 
313
333
  self.history.record("save", {"path": str(path)})
314
334
 
@@ -401,15 +421,20 @@ class Session:
401
421
  return "\n".join(lines)
402
422
 
403
423
 
404
- def load_session(path: str | Path) -> Session:
405
- """Load session from file.
424
+ def load_session(path: str | Path, *, verify_signature: bool = True) -> Session:
425
+ """Load session from file with optional signature verification.
406
426
 
407
427
  Args:
408
428
  path: Path to session file (.tks).
429
+ verify_signature: Verify HMAC signature (default: True). Set to False
430
+ only when loading legacy session files without signatures.
409
431
 
410
432
  Returns:
411
433
  Loaded Session object.
412
434
 
435
+ Raises:
436
+ SecurityError: If signature verification fails.
437
+
413
438
  Example:
414
439
  >>> session = load_session('debug_session.tks')
415
440
  >>> print(session.list_traces())
@@ -419,19 +444,61 @@ def load_session(path: str | Path) -> Session:
419
444
  trusted sources. Loading a malicious .tks file could execute arbitrary
420
445
  code. Never load session files from untrusted or unknown sources.
421
446
 
447
+ New session files include HMAC signatures for integrity verification.
448
+ Legacy files without signatures will trigger a warning.
449
+
422
450
  For secure data exchange, consider exporting to JSON or HDF5 formats
423
451
  instead of using pickle-based session files.
424
452
  """
425
453
  path = Path(path)
426
454
 
455
+ # Helper to load with signature verification
456
+ def _load_with_verification(f: Any, is_compressed: bool = False) -> dict[str, Any]:
457
+ # Read magic bytes to detect format
458
+ magic = f.read(len(_SESSION_MAGIC))
459
+
460
+ if magic == _SESSION_MAGIC:
461
+ # New format with signature
462
+ signature = f.read(_SESSION_SIGNATURE_SIZE)
463
+ serialized = f.read()
464
+
465
+ if verify_signature:
466
+ # Verify HMAC signature
467
+ expected = hmac.new(_SECURITY_KEY, serialized, hashlib.sha256).digest()
468
+ if not hmac.compare_digest(signature, expected):
469
+ raise SecurityError(
470
+ "Session file signature verification failed",
471
+ file_path=str(path),
472
+ check_type="HMAC signature",
473
+ details="File may be corrupted or tampered with",
474
+ )
475
+
476
+ # Deserialize verified data
477
+ data = cast("dict[str, Any]", pickle.loads(serialized))
478
+ else:
479
+ # Legacy format without signature
480
+ warnings.warn(
481
+ f"Loading legacy session file without signature verification: {path}. "
482
+ "Re-save the session to enable security features.",
483
+ UserWarning,
484
+ stacklevel=3,
485
+ )
486
+
487
+ # Rewind and load as legacy pickle
488
+ f.seek(0)
489
+ data = cast("dict[str, Any]", pickle.load(f))
490
+
491
+ return data
492
+
493
+ # Try loading (compressed or uncompressed)
427
494
  try:
428
495
  # Try gzip compressed first
429
496
  with gzip.open(path, "rb") as f:
430
- data = pickle.load(f)
497
+ data = _load_with_verification(f, is_compressed=True)
431
498
  except gzip.BadGzipFile:
432
499
  # Fall back to uncompressed
433
- with open(path, "rb") as f:
434
- data = pickle.load(f)
500
+ with open(path, "rb") as f: # type: ignore[assignment]
501
+ data = _load_with_verification(f, is_compressed=False)
435
502
 
436
503
  session = Session._from_dict(data)
437
504
  session._file_path = path
@@ -4,6 +4,8 @@ Provides utilities for generating synthetic test data with known properties
4
4
  for validation and testing purposes.
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  import struct
8
10
  from dataclasses import dataclass, field
9
11
  from pathlib import Path
oscura/ui/formatters.py CHANGED
@@ -19,10 +19,12 @@ from __future__ import annotations
19
19
 
20
20
  from dataclasses import dataclass
21
21
  from enum import Enum
22
- from typing import Any, Literal
22
+ from typing import Any, Literal, TypeAlias
23
23
 
24
24
  # Type alias for color names accepted by colorize()
25
- type ColorName = Literal["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
25
+ ColorName: TypeAlias = Literal[
26
+ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"
27
+ ]
26
28
 
27
29
 
28
30
  class Color(Enum):
oscura/utils/buffer.py CHANGED
@@ -16,7 +16,7 @@ References:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from typing import TYPE_CHECKING, Any, TypeVar, overload
19
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload
20
20
 
21
21
  import numpy as np
22
22
 
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
26
26
  T = TypeVar("T")
27
27
 
28
28
 
29
- class CircularBuffer[T]:
29
+ class CircularBuffer(Generic[T]):
30
30
  """Fixed-size circular buffer with O(1) operations.
31
31
 
32
32
  Thread-safe for single producer, single consumer pattern.
oscura/utils/lazy.py CHANGED
@@ -18,18 +18,18 @@ References:
18
18
  from __future__ import annotations
19
19
 
20
20
  from abc import ABC, abstractmethod
21
- from typing import TYPE_CHECKING, Any, TypeVar
21
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
22
22
 
23
23
  import numpy as np
24
24
  from numpy.typing import NDArray
25
25
 
26
+ T = TypeVar("T")
27
+
26
28
  if TYPE_CHECKING:
27
29
  from collections.abc import Callable
28
30
 
29
- T = TypeVar("T")
30
-
31
31
 
32
- class LazyProxy[T](ABC):
32
+ class LazyProxy(ABC, Generic[T]):
33
33
  """Abstract base class for lazy evaluation proxies.
34
34
 
35
35
  Defers computation until explicitly requested via .compute().
@@ -169,7 +169,7 @@ class LazyOperation(LazyProxy[Any]):
169
169
  return self._operation(*evaluated_operands, **self._kwargs)
170
170
 
171
171
 
172
- def lazy_operation[T](
172
+ def lazy_operation(
173
173
  func: Callable[..., T],
174
174
  *args: Any,
175
175
  **kwargs: Any,
@@ -20,7 +20,7 @@ from collections import OrderedDict
20
20
  from dataclasses import dataclass
21
21
  from enum import Enum
22
22
  from pathlib import Path
23
- from typing import TYPE_CHECKING, Any, TypeVar
23
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
24
24
 
25
25
  import numpy as np
26
26
 
@@ -664,7 +664,7 @@ T = TypeVar("T")
664
664
 
665
665
 
666
666
  @dataclass
667
- class CacheEntry[T]:
667
+ class CacheEntry(Generic[T]):
668
668
  """Cache entry with metadata.
669
669
 
670
670
  Attributes:
@@ -22,7 +22,7 @@ import hashlib
22
22
  import os
23
23
  import time
24
24
  from collections import OrderedDict
25
- from typing import TYPE_CHECKING, Any, TypeVar
25
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
26
26
 
27
27
  import numpy as np
28
28
 
@@ -102,7 +102,7 @@ class ArrayManager(ResourceManager):
102
102
  # =============================================================================
103
103
 
104
104
 
105
- class LRUCache[T]:
105
+ class LRUCache(Generic[T]):
106
106
  """Least-Recently-Used cache with memory-based eviction.
107
107
 
108
108
  Caches intermediate results with automatic eviction when
@@ -14,8 +14,6 @@ References:
14
14
  ColorBrewer schemes
15
15
  """
16
16
 
17
- from __future__ import annotations
18
-
19
17
  from typing import Literal
20
18
 
21
19
  import numpy as np
@@ -5,6 +5,8 @@ This module provides comprehensive power visualization including
5
5
  time-domain plots, energy accumulation, and multi-channel views.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  from pathlib import Path
9
11
 
10
12
  import matplotlib.pyplot as plt
@@ -3,6 +3,8 @@
3
3
  Provides workflows for processing and analyzing multiple traces together.
4
4
  """
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import concurrent.futures
7
9
  from collections.abc import Iterator
8
10
  from dataclasses import dataclass, field
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oscura
3
- Version: 0.1.2
3
+ Version: 0.3.0
4
4
  Summary: Unified hardware reverse engineering framework. Extract all information from any system through signals and data. Unknown protocol discovery, state machine extraction, CRC recovery, security analysis. 16+ protocols, IEEE-compliant measurements.
5
5
  Project-URL: Homepage, https://github.com/oscura-re/oscura
6
6
  Project-URL: Documentation, https://github.com/oscura-re/oscura/tree/main/docs
@@ -39,7 +39,7 @@ Requires-Dist: pyyaml<7.0.0,>=6.0
39
39
  Requires-Dist: scipy<2.0.0,>=1.10.0
40
40
  Requires-Dist: tm-data-types<1.0.0,>=0.3.0
41
41
  Provides-Extra: all
42
- Requires-Dist: asammdf<8.0.0,>=7.4.0; extra == 'all'
42
+ Requires-Dist: asammdf<9.0.0,>=8.0.0; extra == 'all'
43
43
  Requires-Dist: cantools<40.0.0,>=39.4.0; extra == 'all'
44
44
  Requires-Dist: check-jsonschema<1.0.0,>=0.29.0; extra == 'all'
45
45
  Requires-Dist: h5py<4.0.0,>=3.0.0; extra == 'all'
@@ -51,6 +51,7 @@ Requires-Dist: nbconvert<8.0.0,>=7.0.0; extra == 'all'
51
51
  Requires-Dist: networkx<4.0.0,>=3.0; extra == 'all'
52
52
  Requires-Dist: nptdms<2.0.0,>=1.7.0; extra == 'all'
53
53
  Requires-Dist: openpyxl<4.0.0,>=3.0.0; extra == 'all'
54
+ Requires-Dist: pytest-benchmark<6.0.0,>=4.0.0; extra == 'all'
54
55
  Requires-Dist: pytest-cov<8.0.0,>=6.0; extra == 'all'
55
56
  Requires-Dist: pytest-timeout<3.0.0,>=2.3.0; extra == 'all'
56
57
  Requires-Dist: pytest<10.0.0,>=8.0; extra == 'all'
@@ -67,7 +68,7 @@ Requires-Dist: networkx<4.0.0,>=3.0; extra == 'analysis'
67
68
  Requires-Dist: openpyxl<4.0.0,>=3.0.0; extra == 'analysis'
68
69
  Requires-Dist: pywavelets<2.0.0,>=1.0.0; extra == 'analysis'
69
70
  Provides-Extra: automotive
70
- Requires-Dist: asammdf<8.0.0,>=7.4.0; extra == 'automotive'
71
+ Requires-Dist: asammdf<9.0.0,>=8.0.0; extra == 'automotive'
71
72
  Requires-Dist: cantools<40.0.0,>=39.4.0; extra == 'automotive'
72
73
  Requires-Dist: python-can<5.0.0,>=4.4.0; extra == 'automotive'
73
74
  Requires-Dist: scapy<3.0.0,>=2.5.0; extra == 'automotive'
@@ -75,6 +76,7 @@ Provides-Extra: dev
75
76
  Requires-Dist: check-jsonschema<1.0.0,>=0.29.0; extra == 'dev'
76
77
  Requires-Dist: hypothesis<7.0.0,>=6.0.0; extra == 'dev'
77
78
  Requires-Dist: interrogate<2.0.0,>=1.7.0; extra == 'dev'
79
+ Requires-Dist: pytest-benchmark<6.0.0,>=4.0.0; extra == 'dev'
78
80
  Requires-Dist: pytest-cov<8.0.0,>=6.0; extra == 'dev'
79
81
  Requires-Dist: pytest-timeout<3.0.0,>=2.3.0; extra == 'dev'
80
82
  Requires-Dist: pytest<10.0.0,>=8.0; extra == 'dev'
@@ -98,10 +100,26 @@ Description-Content-Type: text/markdown
98
100
 
99
101
  **Unified hardware reverse engineering framework. Extract all information from any system through signals and data.**
100
102
 
101
- [![PyPI version](https://badge.fury.io/py/oscura.svg)](https://badge.fury.io/py/oscura)
103
+ **Build Status:**
104
+ [![CI](https://github.com/oscura-re/oscura/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/oscura-re/oscura/actions/workflows/ci.yml)
105
+ [![Code Quality](https://github.com/oscura-re/oscura/actions/workflows/code-quality.yml/badge.svg?branch=main)](https://github.com/oscura-re/oscura/actions/workflows/code-quality.yml)
106
+ [![Documentation](https://github.com/oscura-re/oscura/actions/workflows/docs.yml/badge.svg?branch=main)](https://github.com/oscura-re/oscura/actions/workflows/docs.yml)
107
+ [![Test Quality](https://github.com/oscura-re/oscura/actions/workflows/test-quality.yml/badge.svg?branch=main)](https://github.com/oscura-re/oscura/actions/workflows/test-quality.yml)
108
+
109
+ **Package:**
110
+ [![PyPI version](https://img.shields.io/pypi/v/oscura)](https://pypi.org/project/oscura/)
102
111
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
103
112
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
104
- [![CI](https://github.com/oscura-re/oscura/workflows/CI/badge.svg)](https://github.com/oscura-re/oscura/actions)
113
+ [![PyPI Downloads](https://img.shields.io/pypi/dm/oscura)](https://pypi.org/project/oscura/)
114
+
115
+ **Code Quality:**
116
+ [![codecov](https://codecov.io/gh/oscura-re/oscura/graph/badge.svg)](https://codecov.io/gh/oscura-re/oscura)
117
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
118
+ [![Docstring Coverage](https://raw.githubusercontent.com/oscura-re/oscura/main/docs/badges/interrogate_badge.svg)](https://github.com/oscura-re/oscura/tree/main/docs)
119
+
120
+ **Project Status:**
121
+ [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/oscura-re/oscura/graphs/commit-activity)
122
+ [![Last Commit](https://img.shields.io/github/last-commit/oscura-re/oscura)](https://github.com/oscura-re/oscura/commits/main)
105
123
 
106
124
  ---
107
125
 
@@ -126,11 +144,11 @@ uv pip install oscura
126
144
  # Or with pip
127
145
  pip install oscura
128
146
 
129
- # Development install
147
+ # Development install (RECOMMENDED)
130
148
  git clone https://github.com/oscura-re/oscura.git
131
149
  cd oscura
132
- uv sync --all-extras
133
- ./scripts/setup/install-hooks.sh
150
+ ./scripts/setup.sh # Complete setup (dependencies + hooks)
151
+ ./scripts/verify-setup.sh # Verify environment is ready
134
152
  ```
135
153
 
136
154
  ---
@@ -192,7 +210,7 @@ messages = decoder.decode(trace)
192
210
 
193
211
  ```bash
194
212
  # Generate demo data
195
- python demos/data_generation/generate_all_demo_data.py
213
+ python demos/generate_all_demo_data.py
196
214
 
197
215
  # Run a demo
198
216
  uv run python demos/01_waveform_analysis/comprehensive_wfm_analysis.py
@@ -225,17 +243,20 @@ Tektronix WFM • Rigol WFM • LeCroy TRC • Sigrok • VCD • CSV • NumPy
225
243
  ## Command Line Interface
226
244
 
227
245
  ```bash
228
- # Analyze a waveform
229
- oscura analyze capture.wfm
246
+ # Characterize signal measurements
247
+ oscura characterize capture.wfm
230
248
 
231
249
  # Decode protocol
232
- oscura decode capture.wfm --protocol uart --baud 115200
250
+ oscura decode uart.wfm --protocol uart
251
+
252
+ # Batch process multiple files
253
+ oscura batch '*.wfm' --analysis characterize
233
254
 
234
- # Generate report
235
- oscura report capture.wfm -o report.pdf
255
+ # Compare two signals
256
+ oscura compare before.wfm after.wfm
236
257
 
237
- # Convert formats
238
- oscura convert input.wfm output.csv
258
+ # Interactive shell
259
+ oscura shell
239
260
  ```
240
261
 
241
262
  See [CLI Reference](docs/cli.md) for complete documentation.