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.
- oscura/__init__.py +1 -1
- 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/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 +1 -3
- oscura/automotive/can/__init__.py +0 -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/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 +56 -2
- 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 +67 -51
- 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 +80 -13
- 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.3.0.dist-info}/METADATA +37 -16
- {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/RECORD +91 -89
- {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/WHEEL +0 -0
- {oscura-0.1.2.dist-info → oscura-0.3.0.dist-info}/entry_points.txt +0 -0
- {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
|
|
291
|
-
|
|
292
|
-
with untrusted parties, use JSON or HDF5
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
oscura/testing/synthetic.py
CHANGED
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
|
-
|
|
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]
|
|
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
|
|
172
|
+
def lazy_operation(
|
|
173
173
|
func: Callable[..., T],
|
|
174
174
|
*args: Any,
|
|
175
175
|
**kwargs: Any,
|
oscura/utils/memory_advanced.py
CHANGED
|
@@ -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
|
oscura/visualization/colors.py
CHANGED
oscura/visualization/power.py
CHANGED
oscura/workflows/multi_trace.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oscura
|
|
3
|
-
Version: 0.
|
|
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<
|
|
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<
|
|
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
|
-
|
|
103
|
+
**Build Status:**
|
|
104
|
+
[](https://github.com/oscura-re/oscura/actions/workflows/ci.yml)
|
|
105
|
+
[](https://github.com/oscura-re/oscura/actions/workflows/code-quality.yml)
|
|
106
|
+
[](https://github.com/oscura-re/oscura/actions/workflows/docs.yml)
|
|
107
|
+
[](https://github.com/oscura-re/oscura/actions/workflows/test-quality.yml)
|
|
108
|
+
|
|
109
|
+
**Package:**
|
|
110
|
+
[](https://pypi.org/project/oscura/)
|
|
102
111
|
[](https://www.python.org/downloads/)
|
|
103
112
|
[](https://opensource.org/licenses/MIT)
|
|
104
|
-
[](https://pypi.org/project/oscura/)
|
|
114
|
+
|
|
115
|
+
**Code Quality:**
|
|
116
|
+
[](https://codecov.io/gh/oscura-re/oscura)
|
|
117
|
+
[](https://github.com/astral-sh/ruff)
|
|
118
|
+
[](https://github.com/oscura-re/oscura/tree/main/docs)
|
|
119
|
+
|
|
120
|
+
**Project Status:**
|
|
121
|
+
[](https://github.com/oscura-re/oscura/graphs/commit-activity)
|
|
122
|
+
[](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
|
-
|
|
133
|
-
./scripts/setup
|
|
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/
|
|
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
|
-
#
|
|
229
|
-
oscura
|
|
246
|
+
# Characterize signal measurements
|
|
247
|
+
oscura characterize capture.wfm
|
|
230
248
|
|
|
231
249
|
# Decode protocol
|
|
232
|
-
oscura decode
|
|
250
|
+
oscura decode uart.wfm --protocol uart
|
|
251
|
+
|
|
252
|
+
# Batch process multiple files
|
|
253
|
+
oscura batch '*.wfm' --analysis characterize
|
|
233
254
|
|
|
234
|
-
#
|
|
235
|
-
oscura
|
|
255
|
+
# Compare two signals
|
|
256
|
+
oscura compare before.wfm after.wfm
|
|
236
257
|
|
|
237
|
-
#
|
|
238
|
-
oscura
|
|
258
|
+
# Interactive shell
|
|
259
|
+
oscura shell
|
|
239
260
|
```
|
|
240
261
|
|
|
241
262
|
See [CLI Reference](docs/cli.md) for complete documentation.
|