oscura 0.0.1__py3-none-any.whl → 0.1.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 +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.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"""Plugin isolation and sandboxing.
|
|
2
|
+
|
|
3
|
+
This module provides resource limits, permission models, and sandboxing
|
|
4
|
+
for plugins to prevent interference and ensure security.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import resource
|
|
11
|
+
import threading
|
|
12
|
+
from contextlib import contextmanager, suppress
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import Enum, auto
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Generator
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Permission(Enum):
|
|
24
|
+
"""Plugin permission types.
|
|
25
|
+
|
|
26
|
+
References:
|
|
27
|
+
PLUG-004: Plugin Isolation - permission model
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
READ_CONFIG = auto()
|
|
31
|
+
"""Read configuration files"""
|
|
32
|
+
WRITE_CONFIG = auto()
|
|
33
|
+
"""Write configuration files"""
|
|
34
|
+
READ_DATA = auto()
|
|
35
|
+
"""Read data files"""
|
|
36
|
+
WRITE_DATA = auto()
|
|
37
|
+
"""Write data files"""
|
|
38
|
+
NETWORK_ACCESS = auto()
|
|
39
|
+
"""Access network"""
|
|
40
|
+
SUBPROCESS = auto()
|
|
41
|
+
"""Spawn subprocesses"""
|
|
42
|
+
NATIVE_CODE = auto()
|
|
43
|
+
"""Execute native code"""
|
|
44
|
+
SYSTEM_INFO = auto()
|
|
45
|
+
"""Access system information"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ResourceLimits:
|
|
50
|
+
"""Resource limits for plugin execution.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
max_memory_mb: Maximum memory in MB (None for unlimited)
|
|
54
|
+
max_cpu_time_sec: Maximum CPU time in seconds (None for unlimited)
|
|
55
|
+
max_wall_time_sec: Maximum wall time in seconds (None for unlimited)
|
|
56
|
+
max_file_size_mb: Maximum file size in MB (None for unlimited)
|
|
57
|
+
max_open_files: Maximum open file descriptors (None for unlimited)
|
|
58
|
+
|
|
59
|
+
References:
|
|
60
|
+
PLUG-004: Plugin Isolation - resource limits
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
max_memory_mb: int | None = None
|
|
64
|
+
max_cpu_time_sec: float | None = None
|
|
65
|
+
max_wall_time_sec: float | None = None
|
|
66
|
+
max_file_size_mb: int | None = None
|
|
67
|
+
max_open_files: int | None = None
|
|
68
|
+
|
|
69
|
+
def to_rlimit_dict(self) -> dict[int, tuple[int, int]]:
|
|
70
|
+
"""Convert to resource.setrlimit compatible dict.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dictionary of resource limits
|
|
74
|
+
|
|
75
|
+
References:
|
|
76
|
+
PLUG-004: Plugin Isolation - resource limits
|
|
77
|
+
"""
|
|
78
|
+
limits = {}
|
|
79
|
+
|
|
80
|
+
if self.max_memory_mb is not None:
|
|
81
|
+
# RLIMIT_AS - address space limit
|
|
82
|
+
limit_bytes = self.max_memory_mb * 1024 * 1024
|
|
83
|
+
limits[resource.RLIMIT_AS] = (limit_bytes, limit_bytes)
|
|
84
|
+
|
|
85
|
+
if self.max_cpu_time_sec is not None:
|
|
86
|
+
# RLIMIT_CPU - CPU time limit
|
|
87
|
+
limit_sec = int(self.max_cpu_time_sec)
|
|
88
|
+
limits[resource.RLIMIT_CPU] = (limit_sec, limit_sec)
|
|
89
|
+
|
|
90
|
+
if self.max_file_size_mb is not None:
|
|
91
|
+
# RLIMIT_FSIZE - file size limit
|
|
92
|
+
limit_bytes = self.max_file_size_mb * 1024 * 1024
|
|
93
|
+
limits[resource.RLIMIT_FSIZE] = (limit_bytes, limit_bytes)
|
|
94
|
+
|
|
95
|
+
if self.max_open_files is not None:
|
|
96
|
+
# RLIMIT_NOFILE - number of open files
|
|
97
|
+
limits[resource.RLIMIT_NOFILE] = (
|
|
98
|
+
self.max_open_files,
|
|
99
|
+
self.max_open_files,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return limits
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class PermissionSet:
|
|
107
|
+
"""Set of permissions granted to a plugin.
|
|
108
|
+
|
|
109
|
+
Attributes:
|
|
110
|
+
allowed: Set of allowed permissions
|
|
111
|
+
denied: Set of explicitly denied permissions
|
|
112
|
+
|
|
113
|
+
References:
|
|
114
|
+
PLUG-004: Plugin Isolation - permission model
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
allowed: set[Permission] = field(default_factory=set)
|
|
118
|
+
denied: set[Permission] = field(default_factory=set)
|
|
119
|
+
|
|
120
|
+
def grant(self, permission: Permission) -> None:
|
|
121
|
+
"""Grant a permission.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
permission: Permission to grant
|
|
125
|
+
|
|
126
|
+
References:
|
|
127
|
+
PLUG-004: Plugin Isolation - permission model
|
|
128
|
+
"""
|
|
129
|
+
self.allowed.add(permission)
|
|
130
|
+
if permission in self.denied:
|
|
131
|
+
self.denied.remove(permission)
|
|
132
|
+
|
|
133
|
+
def deny(self, permission: Permission) -> None:
|
|
134
|
+
"""Deny a permission.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
permission: Permission to deny
|
|
138
|
+
|
|
139
|
+
References:
|
|
140
|
+
PLUG-004: Plugin Isolation - permission model
|
|
141
|
+
"""
|
|
142
|
+
self.denied.add(permission)
|
|
143
|
+
if permission in self.allowed:
|
|
144
|
+
self.allowed.remove(permission)
|
|
145
|
+
|
|
146
|
+
def has_permission(self, permission: Permission) -> bool:
|
|
147
|
+
"""Check if permission is granted.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
permission: Permission to check
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
True if permission is granted
|
|
154
|
+
|
|
155
|
+
References:
|
|
156
|
+
PLUG-004: Plugin Isolation - permission model
|
|
157
|
+
"""
|
|
158
|
+
if permission in self.denied:
|
|
159
|
+
return False
|
|
160
|
+
return permission in self.allowed
|
|
161
|
+
|
|
162
|
+
def check(self, permission: Permission) -> None:
|
|
163
|
+
"""Check permission and raise if not granted.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
permission: Permission to check
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
PermissionError: If permission not granted
|
|
170
|
+
|
|
171
|
+
References:
|
|
172
|
+
PLUG-004: Plugin Isolation - permission model
|
|
173
|
+
"""
|
|
174
|
+
if not self.has_permission(permission):
|
|
175
|
+
raise PermissionError(f"Plugin does not have {permission.name} permission")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TimeoutError(Exception):
|
|
179
|
+
"""Exception raised when execution timeout is exceeded."""
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ResourceExceededError(Exception):
|
|
183
|
+
"""Exception raised when resource limit is exceeded."""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class PluginSandbox:
|
|
187
|
+
"""Sandbox for isolated plugin execution.
|
|
188
|
+
|
|
189
|
+
Provides resource limits, timeout enforcement, and permission checking.
|
|
190
|
+
|
|
191
|
+
References:
|
|
192
|
+
PLUG-004: Plugin Isolation - resource limits, permission model
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
def __init__(
|
|
196
|
+
self,
|
|
197
|
+
permissions: PermissionSet | None = None,
|
|
198
|
+
limits: ResourceLimits | None = None,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Initialize sandbox.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
permissions: Permission set for plugin
|
|
204
|
+
limits: Resource limits for plugin
|
|
205
|
+
|
|
206
|
+
References:
|
|
207
|
+
PLUG-004: Plugin Isolation
|
|
208
|
+
"""
|
|
209
|
+
self.permissions = permissions or PermissionSet()
|
|
210
|
+
self.limits = limits or ResourceLimits()
|
|
211
|
+
self._original_limits: dict[int, tuple[int, int]] = {}
|
|
212
|
+
|
|
213
|
+
@contextmanager
|
|
214
|
+
def execute(
|
|
215
|
+
self,
|
|
216
|
+
timeout: float | None = None,
|
|
217
|
+
) -> Generator[None, None, None]:
|
|
218
|
+
"""Execute code in sandbox with resource limits.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
timeout: Execution timeout in seconds
|
|
222
|
+
|
|
223
|
+
Yields:
|
|
224
|
+
None
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
ResourceExceededError: If resource limit exceeded
|
|
228
|
+
|
|
229
|
+
Note:
|
|
230
|
+
Timeout handling is logged but does not raise TimeoutError.
|
|
231
|
+
Future versions will add proper timeout interruption.
|
|
232
|
+
|
|
233
|
+
References:
|
|
234
|
+
PLUG-004: Plugin Isolation - resource limits
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
>>> sandbox = PluginSandbox(limits=ResourceLimits(max_memory_mb=100))
|
|
238
|
+
>>> with sandbox.execute(timeout=5.0):
|
|
239
|
+
... # Plugin code runs here with limits
|
|
240
|
+
... result = plugin.process_data(data)
|
|
241
|
+
"""
|
|
242
|
+
# Apply resource limits
|
|
243
|
+
self._apply_limits()
|
|
244
|
+
|
|
245
|
+
# Setup timeout if specified
|
|
246
|
+
timer = None
|
|
247
|
+
if timeout is not None:
|
|
248
|
+
try:
|
|
249
|
+
timer = threading.Timer(timeout, self._timeout_handler)
|
|
250
|
+
timer.start()
|
|
251
|
+
except RuntimeError:
|
|
252
|
+
# Thread limit reached, skip timeout
|
|
253
|
+
logger.warning("Could not create timeout timer (thread limit reached)")
|
|
254
|
+
timer = None
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
yield
|
|
258
|
+
|
|
259
|
+
except MemoryError as e:
|
|
260
|
+
raise ResourceExceededError("Memory limit exceeded") from e
|
|
261
|
+
|
|
262
|
+
finally:
|
|
263
|
+
# Cancel timeout timer
|
|
264
|
+
if timer is not None:
|
|
265
|
+
with suppress(Exception):
|
|
266
|
+
timer.cancel()
|
|
267
|
+
|
|
268
|
+
# Restore original limits
|
|
269
|
+
self._restore_limits()
|
|
270
|
+
|
|
271
|
+
def _apply_limits(self) -> None:
|
|
272
|
+
"""Apply resource limits.
|
|
273
|
+
|
|
274
|
+
References:
|
|
275
|
+
PLUG-004: Plugin Isolation - resource limits
|
|
276
|
+
"""
|
|
277
|
+
rlimits = self.limits.to_rlimit_dict()
|
|
278
|
+
|
|
279
|
+
for resource_type, limit in rlimits.items():
|
|
280
|
+
try:
|
|
281
|
+
# Save original limit
|
|
282
|
+
self._original_limits[resource_type] = resource.getrlimit(resource_type)
|
|
283
|
+
# Apply new limit
|
|
284
|
+
resource.setrlimit(resource_type, limit)
|
|
285
|
+
logger.debug(f"Applied resource limit: {resource_type} = {limit}")
|
|
286
|
+
|
|
287
|
+
except (OSError, ValueError) as e:
|
|
288
|
+
logger.warning(f"Failed to set resource limit {resource_type}: {e}")
|
|
289
|
+
|
|
290
|
+
def _restore_limits(self) -> None:
|
|
291
|
+
"""Restore original resource limits.
|
|
292
|
+
|
|
293
|
+
References:
|
|
294
|
+
PLUG-004: Plugin Isolation - resource limits
|
|
295
|
+
"""
|
|
296
|
+
for resource_type, limit in self._original_limits.items():
|
|
297
|
+
try:
|
|
298
|
+
resource.setrlimit(resource_type, limit)
|
|
299
|
+
except (OSError, ValueError) as e:
|
|
300
|
+
logger.warning(f"Failed to restore resource limit {resource_type}: {e}")
|
|
301
|
+
|
|
302
|
+
self._original_limits.clear()
|
|
303
|
+
|
|
304
|
+
def _timeout_handler(self) -> None:
|
|
305
|
+
"""Handle execution timeout.
|
|
306
|
+
|
|
307
|
+
References:
|
|
308
|
+
PLUG-004: Plugin Isolation - CPU time limit
|
|
309
|
+
"""
|
|
310
|
+
logger.error("Plugin execution timeout exceeded")
|
|
311
|
+
# In a real implementation, this would interrupt the thread
|
|
312
|
+
# For now, we just log the error
|
|
313
|
+
|
|
314
|
+
def check_permission(self, permission: Permission) -> None:
|
|
315
|
+
"""Check if plugin has permission.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
permission: Permission to check
|
|
319
|
+
|
|
320
|
+
References:
|
|
321
|
+
PLUG-004: Plugin Isolation - permission model
|
|
322
|
+
"""
|
|
323
|
+
self.permissions.check(permission)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class IsolationManager:
|
|
327
|
+
"""Manager for plugin isolation and sandboxing.
|
|
328
|
+
|
|
329
|
+
Tracks sandboxes for all plugins and enforces isolation policies.
|
|
330
|
+
|
|
331
|
+
References:
|
|
332
|
+
PLUG-004: Plugin Isolation
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
def __init__(self) -> None:
|
|
336
|
+
"""Initialize isolation manager."""
|
|
337
|
+
self._sandboxes: dict[str, PluginSandbox] = {}
|
|
338
|
+
self._default_limits = ResourceLimits(
|
|
339
|
+
max_memory_mb=512, # 512 MB default
|
|
340
|
+
max_cpu_time_sec=30.0, # 30 seconds default
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def create_sandbox(
|
|
344
|
+
self,
|
|
345
|
+
plugin_name: str,
|
|
346
|
+
permissions: PermissionSet | None = None,
|
|
347
|
+
limits: ResourceLimits | None = None,
|
|
348
|
+
) -> PluginSandbox:
|
|
349
|
+
"""Create sandbox for a plugin.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
plugin_name: Plugin name
|
|
353
|
+
permissions: Permission set (None for default)
|
|
354
|
+
limits: Resource limits (None for default)
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Plugin sandbox
|
|
358
|
+
|
|
359
|
+
References:
|
|
360
|
+
PLUG-004: Plugin Isolation
|
|
361
|
+
"""
|
|
362
|
+
if limits is None:
|
|
363
|
+
limits = self._default_limits
|
|
364
|
+
|
|
365
|
+
sandbox = PluginSandbox(permissions=permissions, limits=limits)
|
|
366
|
+
self._sandboxes[plugin_name] = sandbox
|
|
367
|
+
|
|
368
|
+
logger.info(f"Created sandbox for plugin '{plugin_name}'")
|
|
369
|
+
return sandbox
|
|
370
|
+
|
|
371
|
+
def get_sandbox(self, plugin_name: str) -> PluginSandbox | None:
|
|
372
|
+
"""Get sandbox for a plugin.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
plugin_name: Plugin name
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Plugin sandbox or None
|
|
379
|
+
"""
|
|
380
|
+
return self._sandboxes.get(plugin_name)
|
|
381
|
+
|
|
382
|
+
def remove_sandbox(self, plugin_name: str) -> None:
|
|
383
|
+
"""Remove sandbox for a plugin.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
plugin_name: Plugin name
|
|
387
|
+
"""
|
|
388
|
+
if plugin_name in self._sandboxes:
|
|
389
|
+
del self._sandboxes[plugin_name]
|
|
390
|
+
logger.info(f"Removed sandbox for plugin '{plugin_name}'")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# Global isolation manager
|
|
394
|
+
_isolation_manager: IsolationManager | None = None
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def get_isolation_manager() -> IsolationManager:
|
|
398
|
+
"""Get global isolation manager.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Global IsolationManager instance
|
|
402
|
+
"""
|
|
403
|
+
global _isolation_manager
|
|
404
|
+
if _isolation_manager is None:
|
|
405
|
+
_isolation_manager = IsolationManager()
|
|
406
|
+
return _isolation_manager
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
__all__ = [
|
|
410
|
+
"IsolationManager",
|
|
411
|
+
"Permission",
|
|
412
|
+
"PermissionSet",
|
|
413
|
+
"PluginSandbox",
|
|
414
|
+
"ResourceExceededError",
|
|
415
|
+
"ResourceLimits",
|
|
416
|
+
"TimeoutError",
|
|
417
|
+
"get_isolation_manager",
|
|
418
|
+
]
|