oscura 0.0.1__py3-none-any.whl → 0.1.1__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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
oscura/core/audit.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""Audit trail with HMAC chain verification for compliance and tamper detection.
|
|
2
|
+
|
|
3
|
+
This module provides tamper-evident audit logging using HMAC signatures
|
|
4
|
+
to create a verifiable chain of audit entries.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.audit import AuditTrail
|
|
9
|
+
>>> audit = AuditTrail(secret_key=b"my-secret-key")
|
|
10
|
+
>>> audit.record_action("load_trace", {"file": "data.bin"}, user="alice")
|
|
11
|
+
>>> audit.record_action("compute_fft", {"samples": 1000000}, user="alice")
|
|
12
|
+
>>> # Verify integrity
|
|
13
|
+
>>> is_valid = audit.verify_integrity()
|
|
14
|
+
>>> # Export audit log
|
|
15
|
+
>>> audit.export_audit_log("audit.json", format="json")
|
|
16
|
+
|
|
17
|
+
References:
|
|
18
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
19
|
+
HMAC-SHA256 for tamper detection
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import getpass
|
|
25
|
+
import hashlib
|
|
26
|
+
import hmac
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import socket
|
|
30
|
+
from dataclasses import asdict, dataclass, field
|
|
31
|
+
from datetime import UTC, datetime
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any, Literal
|
|
34
|
+
|
|
35
|
+
from oscura.core.logging import format_timestamp
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class AuditEntry:
|
|
40
|
+
"""Single audit trail entry with HMAC signature.
|
|
41
|
+
|
|
42
|
+
Each entry records an auditable action and is linked to the previous
|
|
43
|
+
entry via HMAC chaining for tamper detection.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
timestamp: ISO 8601 timestamp (UTC) of the action.
|
|
47
|
+
action: Action identifier (e.g., "load_trace", "compute_fft").
|
|
48
|
+
details: Additional details about the action (parameters, results).
|
|
49
|
+
user: Username who performed the action (defaults to current user).
|
|
50
|
+
host: Hostname where the action was performed.
|
|
51
|
+
previous_hash: HMAC of the previous entry (for chain verification).
|
|
52
|
+
hmac: HMAC signature of this entry.
|
|
53
|
+
|
|
54
|
+
References:
|
|
55
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
timestamp: str
|
|
59
|
+
action: str
|
|
60
|
+
details: dict[str, Any]
|
|
61
|
+
user: str
|
|
62
|
+
host: str
|
|
63
|
+
previous_hash: str
|
|
64
|
+
hmac: str = field(default="")
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> dict[str, Any]:
|
|
67
|
+
"""Convert audit entry to dictionary.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Dictionary representation of the audit entry.
|
|
71
|
+
"""
|
|
72
|
+
return asdict(self)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_dict(cls, data: dict[str, Any]) -> AuditEntry:
|
|
76
|
+
"""Create audit entry from dictionary.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
data: Dictionary containing audit entry data.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
AuditEntry instance.
|
|
83
|
+
"""
|
|
84
|
+
return cls(**data)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AuditTrail:
|
|
88
|
+
"""Tamper-evident audit trail with HMAC chain verification.
|
|
89
|
+
|
|
90
|
+
Maintains a chain of audit entries where each entry is cryptographically
|
|
91
|
+
linked to the previous entry using HMAC signatures. This allows detection
|
|
92
|
+
of any tampering or modification of the audit log.
|
|
93
|
+
|
|
94
|
+
The HMAC chain works as follows:
|
|
95
|
+
1. Each entry contains the HMAC of the previous entry
|
|
96
|
+
2. Each entry's HMAC is computed over: timestamp + action + details + user + previous_hash
|
|
97
|
+
3. Any modification to any entry breaks the chain and fails verification
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
secret_key: Secret key for HMAC computation (required for tamper detection).
|
|
101
|
+
hash_algorithm: Hash algorithm to use (default: 'sha256').
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
>>> audit = AuditTrail(secret_key=b"my-secret")
|
|
105
|
+
>>> audit.record_action("operation", {"param": "value"})
|
|
106
|
+
>>> assert audit.verify_integrity()
|
|
107
|
+
|
|
108
|
+
References:
|
|
109
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
secret_key: bytes | None = None,
|
|
115
|
+
hash_algorithm: Literal["sha256", "sha512"] = "sha256",
|
|
116
|
+
):
|
|
117
|
+
"""Initialize audit trail.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
secret_key: Secret key for HMAC computation. If None, a random key is generated.
|
|
121
|
+
hash_algorithm: Hash algorithm to use (sha256 or sha512).
|
|
122
|
+
"""
|
|
123
|
+
self._entries: list[AuditEntry] = []
|
|
124
|
+
self._secret_key = secret_key or os.urandom(32)
|
|
125
|
+
self._hash_algorithm = hash_algorithm
|
|
126
|
+
|
|
127
|
+
def record_action(
|
|
128
|
+
self,
|
|
129
|
+
action: str,
|
|
130
|
+
details: dict[str, Any],
|
|
131
|
+
user: str | None = None,
|
|
132
|
+
) -> AuditEntry:
|
|
133
|
+
"""Record an auditable action.
|
|
134
|
+
|
|
135
|
+
Creates a new audit entry with HMAC signature and adds it to the chain.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
action: Action identifier (e.g., "load_trace", "compute_measurement").
|
|
139
|
+
details: Dictionary of action details (parameters, results, etc.).
|
|
140
|
+
user: Username who performed the action (defaults to current user).
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
The created AuditEntry.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> audit = AuditTrail(secret_key=b"key")
|
|
147
|
+
>>> entry = audit.record_action(
|
|
148
|
+
... "load_trace",
|
|
149
|
+
... {"file": "data.bin", "size_mb": 100},
|
|
150
|
+
... user="alice"
|
|
151
|
+
... )
|
|
152
|
+
|
|
153
|
+
References:
|
|
154
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
155
|
+
"""
|
|
156
|
+
# Get current user and host
|
|
157
|
+
if user is None:
|
|
158
|
+
try:
|
|
159
|
+
user = getpass.getuser()
|
|
160
|
+
except Exception:
|
|
161
|
+
user = "unknown"
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
host = socket.gethostname()
|
|
165
|
+
except Exception:
|
|
166
|
+
host = "unknown"
|
|
167
|
+
|
|
168
|
+
# Get timestamp
|
|
169
|
+
timestamp = format_timestamp(datetime.now(UTC), format="iso8601")
|
|
170
|
+
|
|
171
|
+
# Get previous hash
|
|
172
|
+
previous_hash = self._entries[-1].hmac if self._entries else "GENESIS"
|
|
173
|
+
|
|
174
|
+
# Create entry (without HMAC initially)
|
|
175
|
+
entry = AuditEntry(
|
|
176
|
+
timestamp=timestamp,
|
|
177
|
+
action=action,
|
|
178
|
+
details=details,
|
|
179
|
+
user=user,
|
|
180
|
+
host=host,
|
|
181
|
+
previous_hash=previous_hash,
|
|
182
|
+
hmac="",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Compute HMAC
|
|
186
|
+
entry.hmac = self._compute_hmac(entry)
|
|
187
|
+
|
|
188
|
+
# Add to chain
|
|
189
|
+
self._entries.append(entry)
|
|
190
|
+
|
|
191
|
+
return entry
|
|
192
|
+
|
|
193
|
+
def verify_integrity(self) -> bool:
|
|
194
|
+
"""Verify HMAC chain integrity.
|
|
195
|
+
|
|
196
|
+
Verifies that:
|
|
197
|
+
1. Each entry's HMAC is valid
|
|
198
|
+
2. Each entry's previous_hash matches the previous entry's HMAC
|
|
199
|
+
3. No entries have been tampered with or removed
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
True if the audit trail is intact and untampered, False otherwise.
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
>>> audit = AuditTrail(secret_key=b"key")
|
|
206
|
+
>>> audit.record_action("action1", {})
|
|
207
|
+
>>> audit.record_action("action2", {})
|
|
208
|
+
>>> assert audit.verify_integrity() # Should be True
|
|
209
|
+
>>> # Tampering with an entry would break the chain
|
|
210
|
+
>>> audit._entries[0].action = "modified"
|
|
211
|
+
>>> assert not audit.verify_integrity() # Should be False
|
|
212
|
+
|
|
213
|
+
References:
|
|
214
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
215
|
+
"""
|
|
216
|
+
if not self._entries:
|
|
217
|
+
return True # Empty trail is valid
|
|
218
|
+
|
|
219
|
+
for i, entry in enumerate(self._entries):
|
|
220
|
+
# Verify HMAC
|
|
221
|
+
expected_hmac = self._compute_hmac(entry)
|
|
222
|
+
if entry.hmac != expected_hmac:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
# Verify previous hash linkage
|
|
226
|
+
if i == 0:
|
|
227
|
+
if entry.previous_hash != "GENESIS":
|
|
228
|
+
return False
|
|
229
|
+
elif entry.previous_hash != self._entries[i - 1].hmac:
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
return True
|
|
233
|
+
|
|
234
|
+
def export_audit_log(
|
|
235
|
+
self,
|
|
236
|
+
path: str,
|
|
237
|
+
format: Literal["json", "csv"] = "json",
|
|
238
|
+
) -> None:
|
|
239
|
+
"""Export audit trail to file.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
path: Path to export file.
|
|
243
|
+
format: Export format (json or csv).
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
ValueError: If format is not supported.
|
|
247
|
+
|
|
248
|
+
Example:
|
|
249
|
+
>>> audit = AuditTrail(secret_key=b"key")
|
|
250
|
+
>>> audit.record_action("test", {})
|
|
251
|
+
>>> audit.export_audit_log("audit.json", format="json")
|
|
252
|
+
|
|
253
|
+
References:
|
|
254
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
255
|
+
"""
|
|
256
|
+
path_obj = Path(path)
|
|
257
|
+
path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
|
|
259
|
+
if format == "json":
|
|
260
|
+
self._export_json(path_obj)
|
|
261
|
+
elif format == "csv":
|
|
262
|
+
self._export_csv(path_obj)
|
|
263
|
+
else:
|
|
264
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
265
|
+
|
|
266
|
+
def get_entries(
|
|
267
|
+
self,
|
|
268
|
+
since: datetime | None = None,
|
|
269
|
+
action_type: str | None = None,
|
|
270
|
+
) -> list[AuditEntry]:
|
|
271
|
+
"""Query audit entries with optional filtering.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
since: Return only entries after this datetime (UTC).
|
|
275
|
+
action_type: Return only entries with this action type.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
List of matching AuditEntry objects.
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
>>> from datetime import datetime, UTC, timedelta
|
|
282
|
+
>>> audit = AuditTrail(secret_key=b"key")
|
|
283
|
+
>>> audit.record_action("load", {})
|
|
284
|
+
>>> audit.record_action("analyze", {})
|
|
285
|
+
>>> # Get all load actions
|
|
286
|
+
>>> loads = audit.get_entries(action_type="load")
|
|
287
|
+
>>> # Get entries from last hour
|
|
288
|
+
>>> recent = audit.get_entries(since=datetime.now(UTC) - timedelta(hours=1))
|
|
289
|
+
|
|
290
|
+
References:
|
|
291
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
292
|
+
"""
|
|
293
|
+
results = self._entries.copy()
|
|
294
|
+
|
|
295
|
+
# Filter by timestamp
|
|
296
|
+
if since is not None:
|
|
297
|
+
since_str = format_timestamp(since, format="iso8601")
|
|
298
|
+
results = [e for e in results if e.timestamp >= since_str]
|
|
299
|
+
|
|
300
|
+
# Filter by action type
|
|
301
|
+
if action_type is not None:
|
|
302
|
+
results = [e for e in results if e.action == action_type]
|
|
303
|
+
|
|
304
|
+
return results
|
|
305
|
+
|
|
306
|
+
def _compute_hmac(self, entry: AuditEntry) -> str:
|
|
307
|
+
"""Compute HMAC signature for an audit entry.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
entry: Audit entry to sign.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Hexadecimal HMAC signature.
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
ValueError: If hash algorithm is unsupported.
|
|
317
|
+
|
|
318
|
+
References:
|
|
319
|
+
LOG-009: HMAC-based tamper detection
|
|
320
|
+
"""
|
|
321
|
+
# Create canonical representation
|
|
322
|
+
canonical = (
|
|
323
|
+
f"{entry.timestamp}|{entry.action}|{json.dumps(entry.details, sort_keys=True)}"
|
|
324
|
+
f"|{entry.user}|{entry.host}|{entry.previous_hash}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Compute HMAC
|
|
328
|
+
if self._hash_algorithm == "sha256":
|
|
329
|
+
h = hmac.new(self._secret_key, canonical.encode("utf-8"), hashlib.sha256)
|
|
330
|
+
elif self._hash_algorithm == "sha512":
|
|
331
|
+
h = hmac.new(self._secret_key, canonical.encode("utf-8"), hashlib.sha512)
|
|
332
|
+
else:
|
|
333
|
+
raise ValueError(f"Unsupported hash algorithm: {self._hash_algorithm}")
|
|
334
|
+
|
|
335
|
+
return h.hexdigest()
|
|
336
|
+
|
|
337
|
+
def _export_json(self, path: Path) -> None:
|
|
338
|
+
"""Export audit trail as JSON.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
path: Path to JSON file.
|
|
342
|
+
"""
|
|
343
|
+
data = {
|
|
344
|
+
"version": "1.0",
|
|
345
|
+
"hash_algorithm": self._hash_algorithm,
|
|
346
|
+
"entries": [entry.to_dict() for entry in self._entries],
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
350
|
+
json.dump(data, f, indent=2)
|
|
351
|
+
|
|
352
|
+
def _export_csv(self, path: Path) -> None:
|
|
353
|
+
"""Export audit trail as CSV.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
path: Path to CSV file.
|
|
357
|
+
"""
|
|
358
|
+
import csv
|
|
359
|
+
|
|
360
|
+
with open(path, "w", newline="", encoding="utf-8") as f:
|
|
361
|
+
if not self._entries:
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
# Get all possible detail keys
|
|
365
|
+
detail_keys = set() # type: ignore[var-annotated]
|
|
366
|
+
for entry in self._entries:
|
|
367
|
+
detail_keys.update(entry.details.keys())
|
|
368
|
+
detail_keys = sorted(detail_keys) # type: ignore[assignment]
|
|
369
|
+
|
|
370
|
+
# Create CSV writer
|
|
371
|
+
fieldnames = [
|
|
372
|
+
"timestamp",
|
|
373
|
+
"action",
|
|
374
|
+
"user",
|
|
375
|
+
"host",
|
|
376
|
+
"previous_hash",
|
|
377
|
+
"hmac",
|
|
378
|
+
] + [f"detail_{k}" for k in detail_keys]
|
|
379
|
+
|
|
380
|
+
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
381
|
+
writer.writeheader()
|
|
382
|
+
|
|
383
|
+
# Write entries
|
|
384
|
+
for entry in self._entries:
|
|
385
|
+
row = {
|
|
386
|
+
"timestamp": entry.timestamp,
|
|
387
|
+
"action": entry.action,
|
|
388
|
+
"user": entry.user,
|
|
389
|
+
"host": entry.host,
|
|
390
|
+
"previous_hash": entry.previous_hash,
|
|
391
|
+
"hmac": entry.hmac,
|
|
392
|
+
}
|
|
393
|
+
# Add details
|
|
394
|
+
for key in detail_keys:
|
|
395
|
+
value = entry.details.get(key)
|
|
396
|
+
row[f"detail_{key}"] = json.dumps(value) if value is not None else ""
|
|
397
|
+
|
|
398
|
+
writer.writerow(row)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# Convenience function for global audit trail
|
|
402
|
+
_global_audit_trail: AuditTrail | None = None
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def get_global_audit_trail(secret_key: bytes | None = None) -> AuditTrail:
|
|
406
|
+
"""Get or create the global audit trail.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
secret_key: Secret key for HMAC computation (only used on first call).
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Global AuditTrail instance.
|
|
413
|
+
|
|
414
|
+
Example:
|
|
415
|
+
>>> from oscura.core.audit import get_global_audit_trail
|
|
416
|
+
>>> audit = get_global_audit_trail(secret_key=b"my-key")
|
|
417
|
+
>>> audit.record_action("test", {})
|
|
418
|
+
|
|
419
|
+
References:
|
|
420
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
421
|
+
"""
|
|
422
|
+
global _global_audit_trail
|
|
423
|
+
if _global_audit_trail is None:
|
|
424
|
+
_global_audit_trail = AuditTrail(secret_key=secret_key)
|
|
425
|
+
return _global_audit_trail
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def record_audit(action: str, details: dict[str, Any], user: str | None = None) -> AuditEntry:
|
|
429
|
+
"""Record an action to the global audit trail.
|
|
430
|
+
|
|
431
|
+
Convenience function for recording to the global audit trail.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
action: Action identifier.
|
|
435
|
+
details: Action details.
|
|
436
|
+
user: Username (defaults to current user).
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Created AuditEntry.
|
|
440
|
+
|
|
441
|
+
Example:
|
|
442
|
+
>>> from oscura.core.audit import record_audit
|
|
443
|
+
>>> record_audit("compute_fft", {"samples": 1000000})
|
|
444
|
+
|
|
445
|
+
References:
|
|
446
|
+
LOG-009: Comprehensive Audit Trail for Compliance
|
|
447
|
+
"""
|
|
448
|
+
audit = get_global_audit_trail()
|
|
449
|
+
return audit.record_action(action, details, user)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
__all__ = [
|
|
453
|
+
"AuditEntry",
|
|
454
|
+
"AuditTrail",
|
|
455
|
+
"get_global_audit_trail",
|
|
456
|
+
"record_audit",
|
|
457
|
+
]
|