mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
106 Commits
copilot/av
...
verus2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5112ccf492 | ||
|
|
8f740e2f6f | ||
|
|
687be2850b | ||
|
|
95bffcb5f9 | ||
|
|
db8a9abf13 | ||
|
|
421ee6af9b | ||
|
|
64f71dee34 | ||
|
|
648ba40126 | ||
|
|
126cc12eb5 | ||
|
|
1a8fc08773 | ||
|
|
c164917d63 | ||
|
|
6a6cc659b7 | ||
|
|
a86cf1119f | ||
|
|
989ca6df2e | ||
|
|
d36f952133 | ||
|
|
35fb5d5953 | ||
|
|
296b34171a | ||
|
|
f9d54cd436 | ||
|
|
5b60daabd9 | ||
|
|
f69974dc1b | ||
|
|
942dd47163 | ||
|
|
ac701b4933 | ||
|
|
86088d2049 | ||
|
|
83891d7782 | ||
|
|
898643129e | ||
|
|
50c0215fdb | ||
|
|
ee3dff9a3d | ||
|
|
b8e15f46f3 | ||
|
|
37144968c8 | ||
|
|
7ee503ccdc | ||
|
|
006e819d52 | ||
|
|
b6f11c5602 | ||
|
|
72033e77da | ||
|
|
be34063dba | ||
|
|
04bf417c06 | ||
|
|
bc23cd08ac | ||
|
|
1c607dc1d3 | ||
|
|
47cc27ff49 | ||
|
|
8814eda0ae | ||
|
|
b4a69a13ba | ||
|
|
e83a47497a | ||
|
|
241c1d445b | ||
|
|
4054d1b6b6 | ||
|
|
8f7ca44bdf | ||
|
|
96360fa9d8 | ||
|
|
455d2aa588 | ||
|
|
0e5fe9b9ac | ||
|
|
0e9e34a519 | ||
|
|
3f7a5496dc | ||
|
|
0316ccd90c | ||
|
|
10eebfe54c | ||
|
|
e688806ca0 | ||
|
|
394625d4bc | ||
|
|
e68e852ee3 | ||
|
|
2b1434b3ac | ||
|
|
fd59bb5a91 | ||
|
|
80686d6ed1 | ||
|
|
9426b2ec02 | ||
|
|
740db8a0f5 | ||
|
|
d626f75421 | ||
|
|
5afbd96159 | ||
|
|
28891ef883 | ||
|
|
49958c2ece | ||
|
|
08a5e00960 | ||
|
|
1d71df30b6 | ||
|
|
249dcd0b43 | ||
|
|
604591a0f7 | ||
|
|
dbfb8e38a8 | ||
|
|
273a80571e | ||
|
|
3f29eb2fa6 | ||
|
|
889a02ddd6 | ||
|
|
70f63a0982 | ||
|
|
6bc1249dc8 | ||
|
|
5d0cf95332 | ||
|
|
fd4bb3081f | ||
|
|
93a633750c | ||
|
|
52b56f4214 | ||
|
|
8b84d4ce12 | ||
|
|
ecf95833f9 | ||
|
|
9fa8036ce4 | ||
|
|
a232b13e50 | ||
|
|
e9a50bcfd5 | ||
|
|
d0fa639bb8 | ||
|
|
a514e8da83 | ||
|
|
252ae0e312 | ||
|
|
ce85e0102d | ||
|
|
a8f5ac6117 | ||
|
|
632f64b2ce | ||
|
|
c41f289b19 | ||
|
|
2a75b3b0b6 | ||
|
|
3962b3c38d | ||
|
|
d4b7d1ff6c | ||
|
|
d6cd738822 | ||
|
|
36e75d3e49 | ||
|
|
befe131048 | ||
|
|
bedf667adc | ||
|
|
8269968c4a | ||
|
|
e3d23766ae | ||
|
|
1d627f3798 | ||
|
|
b7b3d3ec87 | ||
|
|
30bd134a0b | ||
|
|
5aefd51cb6 | ||
|
|
e060e43a6c | ||
|
|
12c083e29e | ||
|
|
a8a3a9809b | ||
|
|
14deaaa5b6 |
2
.github/actions/toolchains/rust/action.yml
vendored
2
.github/actions/toolchains/rust/action.yml
vendored
@@ -4,7 +4,7 @@ inputs:
|
||||
toolchain:
|
||||
description: 'Rust toolchain version'
|
||||
required: false
|
||||
default: '1.89.0'
|
||||
default: '1.92.0'
|
||||
components:
|
||||
description: 'Additional components to install'
|
||||
required: false
|
||||
|
||||
92
.github/dependabot.yml
vendored
92
.github/dependabot.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
@@ -5,7 +7,95 @@
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# All Rust/Cargo directories are grouped into a single entry so that
|
||||
# when a dependency is updated, Dependabot bumps it across the root
|
||||
# workspace AND every binding, preventing version skew.
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/" # Location of package manifests
|
||||
directories:
|
||||
- "/"
|
||||
- "/bindings/ffi"
|
||||
- "/bindings/java"
|
||||
- "/bindings/python"
|
||||
- "/bindings/ruby"
|
||||
- "/bindings/wasm"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
groups:
|
||||
# Bundle all Cargo dependency updates into a single PR. Without this,
|
||||
# dependabot creates a separate PR per directory for the same dependency,
|
||||
# and each individual PR fails to build due to version skew.
|
||||
rust-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
# Ignore vendored mimalloc crates; updates are managed manually.
|
||||
ignore:
|
||||
- dependency-name: "regorus-mimalloc"
|
||||
- dependency-name: "regorus-mimalloc-sys"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/bindings/go"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
groups:
|
||||
per-dependency:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/bindings/java"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
groups:
|
||||
per-dependency:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "nuget"
|
||||
directory: "/bindings/csharp"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
groups:
|
||||
per-dependency:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/bindings/python"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
groups:
|
||||
per-dependency:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "bundler"
|
||||
directory: "/bindings/ruby"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
groups:
|
||||
per-dependency:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
109
.github/workflows/codeql.yml
vendored
109
.github/workflows/codeql.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: "CodeQL Security Analysis"
|
||||
|
||||
on:
|
||||
@@ -60,46 +62,65 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
# Setup language-specific dependencies BEFORE CodeQL init for proper tracing setup
|
||||
- name: Setup Rust
|
||||
if: matrix.language == 'rust' || matrix.language == 'c-cpp'
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
|
||||
- name: Fetch workspace dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Fetch FFI crate dependencies
|
||||
if: matrix.language == 'c-cpp' || matrix.language == 'go' || matrix.language == 'csharp'
|
||||
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
|
||||
|
||||
- name: Fetch Java crate dependencies
|
||||
if: matrix.language == 'java-kotlin'
|
||||
run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
|
||||
|
||||
- name: Setup Python
|
||||
if: matrix.language == 'python'
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Setup Java
|
||||
if: matrix.language == 'java-kotlin'
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
distribution: 'corretto'
|
||||
java-version: '8'
|
||||
|
||||
- name: Setup Go
|
||||
if: matrix.language == 'go'
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Setup .NET
|
||||
if: matrix.language == 'csharp'
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
global-json-file: ./bindings/csharp/global.json
|
||||
|
||||
- name: Invoke dotnet directly
|
||||
if: matrix.language == 'csharp'
|
||||
run: dotnet --info
|
||||
|
||||
- name: Setup Node.js
|
||||
if: matrix.language == 'javascript-typescript'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -120,7 +141,7 @@ jobs:
|
||||
|
||||
- name: Setup Ruby
|
||||
if: matrix.language == 'rust' && contains(matrix.working-directory, 'ruby')
|
||||
uses: ruby/setup-ruby@v1
|
||||
uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1.299.0
|
||||
with:
|
||||
ruby-version: '3.4.2'
|
||||
bundler-cache: true
|
||||
@@ -132,81 +153,41 @@ jobs:
|
||||
cargo install wasm-pack
|
||||
|
||||
# Manual build steps for different languages
|
||||
- name: Build C/C++ FFI bindings
|
||||
- name: Build C/C++ bindings via xtask
|
||||
if: matrix.language == 'c-cpp'
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
run: |
|
||||
# Build FFI library in no_std mode for embedded/constrained environments
|
||||
cargo build --release --locked --features "ast,coverage,regorus/opa-no-std" --no-default-features
|
||||
cargo xtask test-c --release --frozen
|
||||
cargo xtask test-cpp --release --frozen --skip-ffi
|
||||
cargo xtask test-c-no-std --release --frozen --skip-ffi
|
||||
|
||||
# Build the Rust FFI library that provides C-compatible interface
|
||||
cargo build --release --locked
|
||||
|
||||
# Build C bindings using CMake
|
||||
cd ../c
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
|
||||
# Build C++ bindings using CMake
|
||||
cd ../../cpp
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
|
||||
- name: Build Java bindings
|
||||
- name: Build Java bindings via xtask
|
||||
if: matrix.language == 'java-kotlin'
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
run: |
|
||||
# Build the Rust JNI library that provides Java-compatible interface
|
||||
cargo fetch
|
||||
cargo build --release --locked
|
||||
# Compile Java source and create JAR package with Maven
|
||||
mvn package
|
||||
run: cargo xtask test-java --release --frozen
|
||||
|
||||
- name: Build Go bindings
|
||||
- name: Build Go bindings via xtask
|
||||
if: matrix.language == 'go'
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
run: |
|
||||
# Build the FFI library that Go bindings depend on via CGO
|
||||
cd ../ffi
|
||||
cargo fetch
|
||||
cargo build --release --locked
|
||||
cd ../go
|
||||
# Download Go dependencies
|
||||
go mod tidy
|
||||
# Set up environment for CGO linking to Rust FFI library
|
||||
export CGO_ENABLED=1
|
||||
export LD_LIBRARY_PATH="$(pwd)/../ffi/target/release:$LD_LIBRARY_PATH"
|
||||
# Build Go packages with verbose output for CodeQL tracing
|
||||
go build -v ./pkg/regorus
|
||||
go build -v -o regorus_test .
|
||||
run: cargo xtask test-go --release --frozen
|
||||
|
||||
- name: Build C# bindings
|
||||
- name: Build C# bindings manually
|
||||
if: matrix.language == 'csharp'
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
run: |
|
||||
# Temporary workaround: CodeQL's tracer replaces dotnet with a missing shim when cargo xtask test-csharp runs,
|
||||
# so invoke dotnet directly here until the upstream fix lands.
|
||||
# Ideal command once fixed: cargo xtask test-csharp --release
|
||||
# Build the FFI library that C# bindings access via P/Invoke
|
||||
cd ../ffi
|
||||
cargo fetch
|
||||
cargo build --release --locked
|
||||
cd ../csharp
|
||||
# Restore NuGet packages and build .NET assemblies in release mode
|
||||
# Build the main Regorus library project only (tests require packaged version)
|
||||
dotnet restore Regorus/Regorus.csproj
|
||||
dotnet build Regorus/Regorus.csproj --no-restore /p:Configuration=Release /p:IgnoreMissingArtifacts=true
|
||||
|
||||
- name: Build WASM bindings
|
||||
- name: Build WASM bindings via xtask
|
||||
if: matrix.language == 'javascript-typescript'
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
run: |
|
||||
# Build WebAssembly module with wasm-pack for Node.js target
|
||||
cargo fetch
|
||||
wasm-pack build --target nodejs --release
|
||||
run: cargo xtask build-wasm --release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
129
.github/workflows/dependabot-refresh-cargo-lockfiles.yml
vendored
Normal file
129
.github/workflows/dependabot-refresh-cargo-lockfiles.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: dependabot/refresh-cargo-lockfiles
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: ["main"]
|
||||
|
||||
concurrency:
|
||||
group: dependabot-refresh-cargo-lockfiles-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
refresh-cargo-lockfiles:
|
||||
permissions:
|
||||
contents: write
|
||||
if: >-
|
||||
github.event.pull_request.user.login == 'dependabot[bot]' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
rustup toolchain install 1.92.0 --profile minimal
|
||||
rustup override set 1.92.0
|
||||
cargo --version
|
||||
rustc --version
|
||||
|
||||
- name: Refresh affected Cargo lockfiles
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
mapfile -t changed_files < <(git diff --name-only "$base_sha" "$head_sha" -- ':(glob)**/Cargo.toml' ':(glob)**/Cargo.lock')
|
||||
|
||||
if [ "${#changed_files[@]}" -eq 0 ]; then
|
||||
echo "No Cargo manifest or lockfile changes detected."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
declare -A manifests=()
|
||||
for path in "${changed_files[@]}"; do
|
||||
case "$path" in
|
||||
bindings/ffi/*)
|
||||
manifests["bindings/ffi/Cargo.toml"]=1
|
||||
;;
|
||||
bindings/java/*)
|
||||
manifests["bindings/java/Cargo.toml"]=1
|
||||
;;
|
||||
bindings/python/*)
|
||||
manifests["bindings/python/Cargo.toml"]=1
|
||||
;;
|
||||
bindings/ruby/*)
|
||||
manifests["bindings/ruby/Cargo.toml"]=1
|
||||
;;
|
||||
bindings/wasm/*)
|
||||
manifests["bindings/wasm/Cargo.toml"]=1
|
||||
;;
|
||||
*)
|
||||
manifests["Cargo.toml"]=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
for manifest in "${!manifests[@]}"; do
|
||||
echo "Refreshing lockfile for $manifest"
|
||||
cargo metadata \
|
||||
--config 'build.rustc="rustc"' \
|
||||
--config 'build.rustc-wrapper=""' \
|
||||
--config 'build.rustc-workspace-wrapper=""' \
|
||||
--format-version 1 \
|
||||
--all-features \
|
||||
--manifest-path "$manifest" > /dev/null
|
||||
done
|
||||
|
||||
if [[ -n "${manifests[Cargo.toml]+x}" ]]; then
|
||||
echo "Refreshing lockfile for tests/ensure_no_std/Cargo.toml (thumbv7m-none-eabi)"
|
||||
cargo metadata \
|
||||
--config 'build.rustc="rustc"' \
|
||||
--config 'build.rustc-wrapper=""' \
|
||||
--config 'build.rustc-workspace-wrapper=""' \
|
||||
--format-version 1 \
|
||||
--manifest-path tests/ensure_no_std/Cargo.toml \
|
||||
--filter-platform thumbv7m-none-eabi > /dev/null
|
||||
fi
|
||||
|
||||
- name: Commit lockfile refresh
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
mapfile -t lockfiles < <(git ls-files -m -o --exclude-standard -- ':(glob)**/Cargo.lock')
|
||||
|
||||
for lockfile in "${lockfiles[@]}"; do
|
||||
git add "$lockfile"
|
||||
done
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "No Cargo lockfile changes required."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
auth_header=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')
|
||||
trap 'git config --unset-all http.https://github.com/.extraheader' EXIT
|
||||
git config http.https://github.com/.extraheader "AUTHORIZATION: basic ${auth_header}"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -m "build(deps): refresh Cargo lockfiles"
|
||||
git push origin HEAD:${{ github.event.pull_request.head.ref }}
|
||||
66
.github/workflows/dependency-audit.yml
vendored
Normal file
66
.github/workflows/dependency-audit.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Dependency Audits
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: "0 6 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cargo-audit:
|
||||
name: Cargo Audit (${{ matrix.lockfile }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
lockfile:
|
||||
- Cargo.lock
|
||||
- bindings/ffi/Cargo.lock
|
||||
- bindings/java/Cargo.lock
|
||||
- bindings/python/Cargo.lock
|
||||
- bindings/ruby/Cargo.lock
|
||||
- bindings/wasm/Cargo.lock
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run cargo audit
|
||||
uses: rustsec/audit-check@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
lockfile: ${{ matrix.lockfile }}
|
||||
|
||||
cargo-deny:
|
||||
name: Cargo Deny (${{ matrix.manifest }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
manifest:
|
||||
- Cargo.toml
|
||||
- bindings/ffi/Cargo.toml
|
||||
- bindings/java/Cargo.toml
|
||||
- bindings/python/Cargo.toml
|
||||
- bindings/ruby/Cargo.toml
|
||||
- bindings/ruby/ext/regorusrb/Cargo.toml
|
||||
- bindings/wasm/Cargo.toml
|
||||
- tests/ensure_no_std/Cargo.toml
|
||||
- xtask/Cargo.toml
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
|
||||
- name: Run cargo deny
|
||||
uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
command: check
|
||||
command-arguments: advisories bans
|
||||
manifest-path: ${{ matrix.manifest }}
|
||||
82
.github/workflows/feature-matrix.yml
vendored
Normal file
82
.github/workflows/feature-matrix.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Thorough weekly test of non-default feature combinations.
|
||||
# Catches regressions from dependency updates and feature-gating issues
|
||||
# that the fast PR CI checks (cargo check only) would miss at runtime.
|
||||
name: tests/feature-matrix
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run at 3:42 AM UTC every Saturday.
|
||||
- cron: "42 3 * * 6"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
feature-matrix:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Bare minimum: validates that the core interpreter works
|
||||
# without any builtins or optional subsystems.
|
||||
- name: minimal (std + arc)
|
||||
features: std,arc
|
||||
|
||||
# Common library usage pattern (issue #595): consumer enables
|
||||
# std + arc + rvm and relies on indexmap/std propagation.
|
||||
- name: library (std + arc + rvm)
|
||||
features: std,arc,rvm
|
||||
|
||||
# New default after removing mimalloc from full-opa.
|
||||
# Ensures all builtins compile without the allocator.
|
||||
- name: full-opa (no mimalloc)
|
||||
features: std,arc,full-opa
|
||||
|
||||
# Binding-style usage: full-opa with the vendored allocator.
|
||||
# Mirrors how ffi/java/python/ruby bindings are built.
|
||||
- name: full-opa + allocator
|
||||
features: std,arc,full-opa,allocator-memory-limits
|
||||
|
||||
# Selective builtins without full-opa: validates that popular
|
||||
# features can be cherry-picked independently.
|
||||
- name: cherry-picked builtins
|
||||
features: std,arc,rvm,regex,time,semver,cache
|
||||
|
||||
# Observability features only: coverage + cache without the
|
||||
# heavier builtins (regex, time, etc.).
|
||||
- name: observability
|
||||
features: std,arc,rvm,coverage,cache
|
||||
|
||||
# Azure Policy adds jsonschema + dashmap; test it compiles
|
||||
# and runs on top of full-opa.
|
||||
- name: azure-policy
|
||||
features: std,arc,full-opa,azure_policy
|
||||
|
||||
# Azure RBAC adds regex + time + net on top of full-opa.
|
||||
- name: azure-rbac
|
||||
features: std,arc,full-opa,azure-rbac
|
||||
|
||||
# no_std with the OPA-compatible feature set: exercises the
|
||||
# spin_no_std codepath and absence of std-only dependencies.
|
||||
- name: no_std
|
||||
features: arc,opa-no-std
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Rust toolchain
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus-features
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
- name: Build
|
||||
run: cargo build --no-default-features --features "${{ matrix.features }}" --frozen
|
||||
- name: Test
|
||||
run: cargo test --no-default-features --features "${{ matrix.features }}" --frozen
|
||||
29
.github/workflows/miri.yml
vendored
Normal file
29
.github/workflows/miri.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: miri
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run at 6:30 AM UTC every Wednesday
|
||||
- cron: "30 6 * * 3"
|
||||
|
||||
jobs:
|
||||
miri-test:
|
||||
name: miri (nightly)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MIRIFLAGS: "-Zmiri-disable-isolation"
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: miri rust-src
|
||||
- name: Set up Miri
|
||||
run: cargo miri setup
|
||||
- name: Run Miri tests
|
||||
run: cargo miri test -p regorus
|
||||
- name: Run Miri ACI tests
|
||||
run: cargo miri test -p regorus --test aci
|
||||
- name: Run Miri kata tests
|
||||
run: cargo miri test -p regorus --test kata
|
||||
29
.github/workflows/pr-extensions.yml
vendored
29
.github/workflows/pr-extensions.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: tests/release-extensions
|
||||
|
||||
on:
|
||||
@@ -18,21 +20,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Rust toolchain
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
- name: Build only std
|
||||
run: cargo build -r --example regorus --no-default-features --features "std,rego-extensions"
|
||||
- name: Doc Tests
|
||||
run: cargo test -r --doc --features rego-extensions
|
||||
- name: Run tests
|
||||
run: cargo test -r --features rego-extensions
|
||||
- name: Run example
|
||||
run: cargo run --example regorus --features rego-extensions -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
|
||||
- name: Run tests (ACI)
|
||||
run: cargo test -r --test aci --features rego-extensions
|
||||
- name: Run tests (KATA)
|
||||
run: cargo test -r --test kata --features rego-extensions
|
||||
- name: Run tests (OPA Conformance)
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
- name: Run rego extensions CI suite
|
||||
run: >-
|
||||
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision,rego-extensions -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
cargo xtask ci-release --frozen --features rego-extensions
|
||||
--skip-all-features-build --skip-no-default-features-tests
|
||||
--skip-azure-policy --skip-azure-rbac
|
||||
--opa-features "opa-testutil,serde_json/arbitrary_precision,rego-extensions"
|
||||
|
||||
42
.github/workflows/pr.yml
vendored
42
.github/workflows/pr.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: tests/release
|
||||
|
||||
on:
|
||||
@@ -18,36 +20,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Rust toolchain
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
- name: Format Check
|
||||
run: cargo fmt --check
|
||||
- name: Fetch
|
||||
run: cargo fetch
|
||||
- name: Build (all features)
|
||||
run: cargo build -r --all-features --frozen
|
||||
- name: Build
|
||||
run: cargo build -r --frozen
|
||||
- name: Test no_std
|
||||
run: cargo test -r --no-default-features --frozen
|
||||
- name: Build only std
|
||||
run: cargo build -r --example regorus --no-default-features --features "std" --frozen
|
||||
- name: Doc Tests
|
||||
run: cargo test -r --doc --frozen
|
||||
- name: Run tests
|
||||
run: cargo test -r --frozen
|
||||
- name: Run example
|
||||
run: cargo run --example regorus --frozen -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
|
||||
- name: Run tests (ACI)
|
||||
run: cargo test -r --test aci --frozen
|
||||
- name: Run tests (KATA)
|
||||
run: cargo test -r --test kata --frozen
|
||||
- name: Run tests (OPA Conformance)
|
||||
run: >-
|
||||
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
- name: Run tests (Azure Policy)
|
||||
run: >-
|
||||
cargo test --frozen --features azure_policy
|
||||
- name: Run tests (Azure RBAC)
|
||||
run: cargo test -r --frozen --features azure-rbac
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
- name: Run release CI suite
|
||||
run: cargo xtask ci-release --frozen
|
||||
|
||||
24
.github/workflows/publish-java.yml
vendored
24
.github/workflows/publish-java.yml
vendored
@@ -1,6 +1,9 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: publish-java
|
||||
|
||||
on: workflow_dispatch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -32,10 +35,10 @@ jobs:
|
||||
os: windows-latest
|
||||
extension: dll
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: 8
|
||||
distribution: "corretto"
|
||||
@@ -43,16 +46,17 @@ jobs:
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||
run: pip install cargo-zigbuild
|
||||
- run: cargo fetch
|
||||
- run: cargo fetch --locked
|
||||
- run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
|
||||
- run: cargo ${{ matrix.build_cmd || 'build' }} --release --frozen --target ${{ matrix.target }}${{ matrix.glibc && format('.{0}', matrix.glibc) || '' }} --manifest-path ./bindings/java/Cargo.toml
|
||||
- run: mkdir -p native/${{ matrix.target }}
|
||||
- run: mv target/${{ matrix.target }}/release/*.${{ matrix.extension }} ./native/${{ matrix.target }}/
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: native-libraries-${{ matrix.target }}
|
||||
path: native/
|
||||
@@ -62,24 +66,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: 8
|
||||
distribution: "corretto"
|
||||
server-id: ossrh
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_PASSWORD
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: native-libraries-*
|
||||
merge-multiple: true
|
||||
path: ./bindings/java/native/
|
||||
- run: mvn package
|
||||
working-directory: ./bindings/java
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: built-jars
|
||||
path: ./bindings/java/target/regorus-java-*.jar
|
||||
|
||||
36
.github/workflows/publish-python.yml
vendored
36
.github/workflows/publish-python.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# This file is autogenerated by maturin v1.4.0
|
||||
# To update, run
|
||||
#
|
||||
@@ -18,28 +20,28 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
|
||||
- name: Build Python extension
|
||||
run: |
|
||||
cargo fetch
|
||||
cargo fetch --locked
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
cargo build --release --target ${{ matrix.target }} --frozen
|
||||
working-directory: bindings/python
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
|
||||
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: wheels-linux-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -50,8 +52,8 @@ jobs:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.10'
|
||||
architecture: ${{ matrix.target }}
|
||||
@@ -59,19 +61,19 @@ jobs:
|
||||
|
||||
- name: Build Python extension
|
||||
run: |
|
||||
cargo fetch
|
||||
cargo fetch --locked
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
cargo build --release --target ${{ matrix.host.target }} --frozen
|
||||
working-directory: bindings/python
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
|
||||
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --manifest-path bindings/python/Cargo.toml --frozen --strip
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: wheels-windows-${{ matrix.target }}
|
||||
path: dist
|
||||
@@ -82,27 +84,27 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, aarch64, universal2-apple-darwin]
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
|
||||
- name: Build Python extension
|
||||
run: |
|
||||
cargo fetch
|
||||
cargo fetch --locked
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
cargo build --release --target ${{ matrix.host.target }} --frozen
|
||||
working-directory: bindings/python
|
||||
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
|
||||
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: wheels-macos-${{ matrix.host.target }}
|
||||
path: dist
|
||||
@@ -114,13 +116,13 @@ jobs:
|
||||
# if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, windows, macos]
|
||||
steps:
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: wheels-*
|
||||
merge-multiple: true
|
||||
path: wheels
|
||||
- name: Publish to PyPI
|
||||
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
|
||||
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
with:
|
||||
|
||||
9
.github/workflows/publish-wasm.yml
vendored
9
.github/workflows/publish-wasm.yml
vendored
@@ -1,10 +1,13 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: publish-wasm
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on: workflow_dispatch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-wasm:
|
||||
@@ -12,11 +15,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
9
.github/workflows/release-plz.yml
vendored
9
.github/workflows/release-plz.yml
vendored
@@ -1,10 +1,13 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: Release-plz
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on: workflow_dispatch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release-plz:
|
||||
@@ -14,13 +17,13 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Rust toolchain
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
- name: Run release-plz
|
||||
uses: MarcoIeni/release-plz-action@8724d33cd97b8295051102e2e19ca592962238f5 #v0.5.108
|
||||
uses: MarcoIeni/release-plz-action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
21
.github/workflows/rust-clippy.yml
vendored
21
.github/workflows/rust-clippy.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
@@ -30,28 +32,27 @@ jobs:
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
|
||||
- name: Install required cargo
|
||||
run: cargo install clippy-sarif sarif-fmt
|
||||
|
||||
- name: Fetch
|
||||
run: cargo fetch
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Run rust-clippy
|
||||
run:
|
||||
cargo clippy
|
||||
--all-features
|
||||
--frozen
|
||||
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
|
||||
|
||||
continue-on-error: true
|
||||
run: cargo xtask clippy --sarif rust-clippy-results.sarif
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@c298edae2d512d807fe4bdc57c0ac5a036f61501 # v3.29.11
|
||||
if: ${{ hashFiles('rust-clippy-results.sarif') != '' }}
|
||||
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.11
|
||||
with:
|
||||
sarif_file: rust-clippy-results.sarif
|
||||
wait-for-processing: true
|
||||
|
||||
46
.github/workflows/test-c-cpp.yml
vendored
46
.github/workflows/test-c-cpp.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/c-cpp
|
||||
|
||||
on:
|
||||
@@ -14,41 +16,29 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Fetch FFI crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
|
||||
|
||||
- name: Setup gcc, g++, cmake, ninja
|
||||
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
|
||||
|
||||
- name: Workaround to ensure that regorus.h is generated
|
||||
run: |
|
||||
cargo fetch
|
||||
cargo build -r --frozen
|
||||
working-directory: ./bindings/ffi
|
||||
|
||||
- name: Test c binding
|
||||
run: |
|
||||
mkdir bindings/c/build
|
||||
cd bindings/c/build
|
||||
cmake -G Ninja ..
|
||||
ninja
|
||||
./regorus_test
|
||||
- name: Test C binding via xtask
|
||||
run: cargo xtask test-c --release --frozen
|
||||
|
||||
- name: Test c-nostd binding
|
||||
run: |
|
||||
mkdir bindings/c-nostd/build
|
||||
cd bindings/c-nostd/build
|
||||
cmake -G Ninja ..
|
||||
ninja
|
||||
./regorus_test
|
||||
- name: Test C (no-std) binding via xtask
|
||||
run: cargo xtask test-c-nostd --release --frozen --skip-ffi
|
||||
|
||||
- name: Test cpp binding
|
||||
run: |
|
||||
mkdir bindings/cpp/build
|
||||
cd bindings/cpp/build
|
||||
cmake -G Ninja ..
|
||||
ninja
|
||||
./regorus_test
|
||||
- name: Test C++ binding via xtask
|
||||
run: cargo xtask test-cpp --release --frozen --skip-ffi
|
||||
|
||||
132
.github/workflows/test-csharp.yml
vendored
132
.github/workflows/test-csharp.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/csharp
|
||||
|
||||
on:
|
||||
@@ -31,36 +33,33 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
libpath: |
|
||||
**/release/libregorus_ffi.so
|
||||
# Disabled for now
|
||||
#- os: macos-latest
|
||||
# target: aarch64-apple-darwin
|
||||
# libpath: |
|
||||
# **/release/libregorus_ffi.dylib
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
libpath: |
|
||||
**/release/libregorus_ffi.dylib
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
|
||||
- name: Fetch crates
|
||||
run: cargo fetch
|
||||
working-directory: ./bindings/ffi
|
||||
with:
|
||||
targets: ${{ matrix.runtime.target }}
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Check Regorus binding formatting
|
||||
run: cargo fmt --check
|
||||
working-directory: ./bindings/ffi
|
||||
- name: Fetch FFI crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml --target ${{ matrix.runtime.target }}
|
||||
|
||||
- name: Check Clippy linting for Regorus binding
|
||||
run: cargo clippy --frozen -- -D warnings
|
||||
working-directory: ./bindings/ffi
|
||||
|
||||
- name: Build Regorus binding
|
||||
run: cargo build -r --target ${{ matrix.runtime.target }} --locked
|
||||
working-directory: ./bindings/ffi
|
||||
- name: Build Regorus FFI via xtask
|
||||
run: cargo xtask build-ffi --release --target ${{ matrix.runtime.target }}
|
||||
|
||||
- name: Upload regorus ffi shared library
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: regorus-ffi-artifacts-${{ matrix.runtime.target }}
|
||||
# Note: The full path of each artifact relative to . is preserved.
|
||||
@@ -68,23 +67,32 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
build-nuget:
|
||||
build-csharp:
|
||||
name: 'Build Regorus nuget'
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-ffi
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
global-json-file: ./bindings/csharp/global.json
|
||||
|
||||
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Download regorus ffi shared libraries
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: regorus-ffi-artifacts-*
|
||||
merge-multiple: true
|
||||
@@ -93,26 +101,22 @@ jobs:
|
||||
- name: Display regorus ffi artifacts
|
||||
run: ls -R ./bindings/csharp/Regorus/tmp
|
||||
|
||||
# Note that we need to supply the target folder within the folder where artifacts are downloaded.
|
||||
- name: Build Regorus binding
|
||||
run: dotnet build /p:Configuration=Release /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target
|
||||
working-directory: ./bindings/csharp/Regorus
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target
|
||||
working-directory: ./bindings/csharp/Regorus
|
||||
- name: Build Regorus nuget via xtask
|
||||
run: cargo xtask build-csharp --release --clean --artifacts-dir ./bindings/csharp/Regorus/tmp/bindings/ffi/target --enforce-artifacts --repository-commit ${{ github.sha }} --include-symbols
|
||||
|
||||
- name: Upload Regorus nuget
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: regorus-nuget
|
||||
path: bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
|
||||
path: |
|
||||
bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
|
||||
bindings/csharp/Regorus/bin/Release/Regorus*.snupkg
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
test-nuget:
|
||||
name: 'Test Regorus Nuget: (${{ matrix.runtime.target }})'
|
||||
needs: build-nuget
|
||||
needs: build-csharp
|
||||
runs-on: ${{ matrix.runtime.os }}
|
||||
strategy:
|
||||
# let us get failures from other jobs even if one fails
|
||||
@@ -123,56 +127,40 @@ jobs:
|
||||
target: x86_64-pc-windows-msvc
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
#- os: macos-latest
|
||||
# target: aarch64-apple-darwin
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
|
||||
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
global-json-file: ./bindings/csharp/global.json
|
||||
|
||||
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Download regorus nuget
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: regorus-nuget
|
||||
path: ./bindings/csharp/regorus-nuget/
|
||||
path: ./bindings/csharp/Regorus/bin/Release
|
||||
|
||||
- name: Restore Regorus.Tests
|
||||
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
|
||||
working-directory: ./bindings/csharp/Regorus.Tests
|
||||
- name: Display regorus nuget
|
||||
run: ls -R ./bindings/csharp/Regorus/bin/Release
|
||||
|
||||
- name: Run Regorus.Tests
|
||||
run: dotnet test --no-restore
|
||||
working-directory: ./bindings/csharp/Regorus.Tests
|
||||
|
||||
- name: Restore TestApp
|
||||
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
|
||||
working-directory: ./bindings/csharp/TestApp
|
||||
|
||||
- name: Build TestApp
|
||||
run: dotnet build --no-restore
|
||||
working-directory: ./bindings/csharp/TestApp
|
||||
|
||||
- name: Run TestApp
|
||||
run: dotnet run --no-build --framework net8.0
|
||||
working-directory: ./bindings/csharp/TestApp
|
||||
|
||||
- name: Restore TargetExampleApp
|
||||
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
|
||||
working-directory: ./bindings/csharp/TargetExampleApp
|
||||
|
||||
- name: Build TargetExampleApp
|
||||
run: dotnet build --no-restore
|
||||
working-directory: ./bindings/csharp/TargetExampleApp
|
||||
|
||||
- name: Run TargetExampleApp
|
||||
run: dotnet run --no-build --framework net8.0
|
||||
working-directory: ./bindings/csharp/TargetExampleApp
|
||||
- name: Run C# tests via xtask
|
||||
run: cargo xtask test-csharp --release --clean --nuget-dir bindings/csharp/Regorus/bin/Release
|
||||
|
||||
|
||||
|
||||
20
.github/workflows/test-ffi.yml
vendored
20
.github/workflows/test-ffi.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/ffi
|
||||
|
||||
on:
|
||||
@@ -14,15 +16,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Fetch FFI crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
|
||||
|
||||
- name: Test FFI
|
||||
run: |
|
||||
cargo fetch
|
||||
cargo build -r --frozen
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
cargo test --features contention_checks --frozen
|
||||
working-directory: ./bindings/ffi
|
||||
run: cargo xtask test-ffi --release --frozen
|
||||
|
||||
30
.github/workflows/test-go.yml
vendored
30
.github/workflows/test-go.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/go
|
||||
|
||||
on:
|
||||
@@ -14,23 +16,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
|
||||
|
||||
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Fetch FFI crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
|
||||
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
architecture: x64
|
||||
|
||||
- name: Build ffi
|
||||
run: cargo build -r
|
||||
working-directory: ./bindings/ffi
|
||||
|
||||
- name: Test go
|
||||
run: |
|
||||
go mod tidy
|
||||
go build
|
||||
LD_LIBRARY_PATH=../ffi/target/release ./regorus_test
|
||||
working-directory: ./bindings/go
|
||||
- name: Test Go binding via xtask
|
||||
run: cargo xtask test-go --release --frozen
|
||||
|
||||
36
.github/workflows/test-java.yml
vendored
36
.github/workflows/test-java.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/java
|
||||
|
||||
on:
|
||||
@@ -14,34 +16,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: 8
|
||||
distribution: "corretto"
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Building binding
|
||||
run: |
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
cargo build --release --manifest-path bindings/java/Cargo.toml --locked
|
||||
- name: Fetch Java crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
|
||||
|
||||
- name: Capture binding version
|
||||
run: |
|
||||
version=$(cargo metadata --manifest-path bindings/java/Cargo.toml --format-version 1 \
|
||||
| jq -r '.packages[] | select(.name == "regorus-java") | .version')
|
||||
echo "REGORUS_JAVA_VERSION=$version" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build jar
|
||||
run: mvn package
|
||||
working-directory: ./bindings/java
|
||||
|
||||
- name: Test jar
|
||||
run: |
|
||||
jar="regorus-java-${REGORUS_JAVA_VERSION}.jar"
|
||||
javac -cp "target/${jar}" Test.java
|
||||
java -Djava.library.path=target/release -cp "target/${jar}:." Test
|
||||
working-directory: ./bindings/java
|
||||
- name: Run Java smoke tests via xtask
|
||||
run: cargo xtask test-java --release --frozen
|
||||
|
||||
27
.github/workflows/test-musl.yml
vendored
27
.github/workflows/test-musl.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: musl
|
||||
|
||||
on:
|
||||
@@ -18,22 +20,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
with:
|
||||
targets: x86_64-unknown-linux-musl
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
- name: Fetch MUSL target dependencies
|
||||
run: cargo fetch --locked --target x86_64-unknown-linux-musl
|
||||
- name: Install musl-gcc
|
||||
run: sudo apt update && sudo apt install -y musl-tools
|
||||
- name: Fetch
|
||||
run: cargo fetch
|
||||
- name: Build (MUSL)
|
||||
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl --frozen
|
||||
- name: Run tests (MUSL)
|
||||
run: cargo test -r --verbose --target x86_64-unknown-linux-musl --frozen
|
||||
- name: Run tests (MUSL ACI)
|
||||
run: cargo test -r --test aci --target x86_64-unknown-linux-musl --frozen
|
||||
- name: Run tests (KATA ACI)
|
||||
run: cargo test -r --test kata --target x86_64-unknown-linux-musl --frozen
|
||||
- name: Run tests (MUSL OPA Conformance)
|
||||
run: >-
|
||||
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
- name: Run MUSL suite via xtask
|
||||
run: cargo xtask test-musl --release --frozen --target x86_64-unknown-linux-musl
|
||||
|
||||
19
.github/workflows/test-no-std.yml
vendored
19
.github/workflows/test-no-std.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/no-std
|
||||
|
||||
on:
|
||||
@@ -18,13 +20,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
with:
|
||||
targets: thumbv7m-none-eabi
|
||||
- name: Fetch
|
||||
run: cargo fetch
|
||||
- name: Build
|
||||
run: cargo build -r --target thumbv7m-none-eabi --frozen
|
||||
working-directory: ./tests/ensure_no_std
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
- name: Fetch ensure_no_std crate dependencies
|
||||
run: cargo fetch --locked --manifest-path tests/ensure_no_std/Cargo.toml --target thumbv7m-none-eabi
|
||||
- name: Test no-std
|
||||
run: cargo xtask test-no-std --release --frozen
|
||||
|
||||
|
||||
88
.github/workflows/test-python.yml
vendored
88
.github/workflows/test-python.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/python
|
||||
|
||||
on:
|
||||
@@ -9,9 +11,6 @@ on:
|
||||
# Run at 8:00 AM every day
|
||||
- cron: "0 8 * * *"
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.10"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@@ -24,70 +23,69 @@ jobs:
|
||||
runs-on: ${{ matrix.host.name }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
targets: ${{ matrix.host.target }}
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Fetch Python crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml --target ${{ matrix.host.target }}
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.10"
|
||||
architecture: x64
|
||||
|
||||
- name: Build Python extension
|
||||
run: |
|
||||
cargo fetch
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
cargo build --release --target ${{ matrix.host.target }} --frozen
|
||||
working-directory: bindings/python
|
||||
- name: Install maturin
|
||||
run: python -m pip install maturin==1.5.1
|
||||
|
||||
- name: Build Wheel
|
||||
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
|
||||
with:
|
||||
target: x86_64
|
||||
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
|
||||
sccache: 'true'
|
||||
- name: Build Python wheel via xtask
|
||||
run: cargo xtask build-python --release --target ${{ matrix.host.target }} --target-dir bindings/python/dist --frozen
|
||||
|
||||
- name: Upload Wheel
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- name: Upload wheel artefacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: regorus-wheel-${{ matrix.host.name }}
|
||||
path: dist/regorus-*.whl
|
||||
path: bindings/python/dist/regorus-*.whl
|
||||
|
||||
test:
|
||||
needs: build
|
||||
strategy:
|
||||
matrix:
|
||||
host: [ubuntu-24.04, ubuntu-22.04, windows-latest]
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||
host:
|
||||
- name: ubuntu-24.04
|
||||
wheel: regorus-0.5.0-cp310-abi3-manylinux_2_34_x86_64.whl
|
||||
- name: ubuntu-22.04
|
||||
wheel: regorus-0.5.0-cp310-abi3-manylinux_2_34_x86_64.whl
|
||||
- name: windows-latest
|
||||
wheel: regorus-0.5.0-cp310-abi3-win_amd64.whl
|
||||
|
||||
needs: build
|
||||
runs-on: ${{ matrix.host.name }}
|
||||
runs-on: ${{ matrix.host }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download Regorus wheel
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
- uses: ./.github/actions/toolchains/rust
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
path: wheels
|
||||
pattern: regorus-wheel-*
|
||||
merge-multiple: true
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- name: Fetch Python crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml
|
||||
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: x64
|
||||
|
||||
- name: Test Wheel
|
||||
run: |
|
||||
pip3 install ../../wheels/${{ matrix.host.wheel }}
|
||||
python3 test.py
|
||||
working-directory: bindings/python
|
||||
- name: Install maturin
|
||||
run: python -m pip install maturin==1.5.1
|
||||
|
||||
- name: Run Python smoke tests via xtask
|
||||
run: cargo xtask test-python --release --python python
|
||||
|
||||
24
.github/workflows/test-ruby.yml
vendored
24
.github/workflows/test-ruby.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/ruby
|
||||
|
||||
on:
|
||||
@@ -12,12 +14,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Ruby and Rust
|
||||
uses: oxidize-rb/actions/setup-ruby-and-rust@7ca44a16e287e5ff7dd72ab53f4bd41cbf34a571 #v1.26
|
||||
uses: oxidize-rb/actions/setup-ruby-and-rust@e5f9a49a7812a078584072f6e3f657ad247c8771 # v1.26
|
||||
with:
|
||||
bundler: 2.6.5
|
||||
rubygems: 3.6.5
|
||||
@@ -27,10 +29,16 @@ jobs:
|
||||
cargo-cache: true
|
||||
working-directory: "bindings/ruby"
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Fetch Ruby crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/ruby/Cargo.toml
|
||||
|
||||
- name: Run ruby tests
|
||||
run: |
|
||||
cd bindings/ruby
|
||||
gem install bundler
|
||||
bundle install
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
bundle exec rake
|
||||
run: cargo xtask test-ruby --release --frozen
|
||||
|
||||
29
.github/workflows/test-wasm.yml
vendored
29
.github/workflows/test-wasm.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: bindings/wasm
|
||||
|
||||
on:
|
||||
@@ -14,28 +16,29 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Fetch WASM crate dependencies
|
||||
run: cargo fetch --locked --manifest-path bindings/wasm/Cargo.toml
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- name: Install wasmlpack
|
||||
run: cargo install wasm-pack
|
||||
|
||||
- name: Test wasm binding
|
||||
run: |
|
||||
cd bindings/wasm
|
||||
cargo fetch
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
wasm-pack build --target nodejs --release
|
||||
# Enable when upstream issue is fixed.
|
||||
# https://github.com/microsoft/regorus/issues/371
|
||||
# wasm-pack test --release --node
|
||||
node test.js
|
||||
- name: Test wasm binding via xtask
|
||||
run: cargo xtask test-wasm --release --frozen --node node
|
||||
|
||||
35
.github/workflows/tests-debug.yml
vendored
35
.github/workflows/tests-debug.yml
vendored
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
name: tests/debug
|
||||
|
||||
on:
|
||||
@@ -18,29 +20,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Rust toolchain
|
||||
uses: ./.github/actions/toolchains/rust
|
||||
- name: Fetch
|
||||
run: cargo fetch
|
||||
- name: Build (all features)
|
||||
run: cargo build --all-features --frozen
|
||||
- name: Build
|
||||
run: cargo build --frozen
|
||||
- name: Test no_std
|
||||
run: cargo test --no-default-features --frozen
|
||||
- name: Build only std
|
||||
run: cargo build --example regorus --no-default-features --features "std" --frozen
|
||||
- name: Doc Tests
|
||||
run: cargo test --doc --frozen
|
||||
- name: Run tests
|
||||
run: cargo test --frozen
|
||||
- name: Run tests (ACI)
|
||||
run: cargo test --test aci --frozen
|
||||
- name: Run tests (KATA)
|
||||
run: cargo test --test kata --frozen
|
||||
- name: Run tests (OPA Conformance)
|
||||
run: >-
|
||||
cargo test --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
- name: Run tests (Azure RBAC)
|
||||
run: cargo test --frozen --features azure-rbac
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: ${{ runner.os }}-regorus
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
- name: Run debug CI suite
|
||||
run: cargo xtask ci-debug --frozen
|
||||
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -25,12 +25,35 @@ bindings/ffi/regorus.ffi.hpp
|
||||
|
||||
bindings/*/target
|
||||
|
||||
# Temporary commit message files
|
||||
.commit-msg.txt
|
||||
|
||||
# Local planning docs
|
||||
docs/plans/
|
||||
|
||||
# C# build folders
|
||||
**bin
|
||||
**obj
|
||||
bindings/csharp/.nuget/
|
||||
|
||||
# Bundler binstubs regenerated during ruby setup
|
||||
bindings/ruby/bin/
|
||||
|
||||
# Visual Studio folders
|
||||
**/*.vs
|
||||
|
||||
# Visual Studio solution files
|
||||
*.sln
|
||||
|
||||
# JetBrains IDEs files
|
||||
.idea/
|
||||
|
||||
# Java build artifacts
|
||||
**/*.class
|
||||
**/*.jar
|
||||
bindings/java/.classpath
|
||||
bindings/java/.project
|
||||
bindings/java/.settings/
|
||||
|
||||
# Emacs temporary files
|
||||
*~
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -6,6 +6,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Azure RBAC condition interpreter with builtin evaluation coverage and YAML test suite, including quantifier (ForAnyOfAnyValues/ForAllOfAllValues), datetime (DateTimeEquals), IP (IpInRange), GUID (GuidEquals), list (ListContains), and string (StringEquals) semantics.
|
||||
- FFI surface for Azure RBAC condition evaluation (see bindings changelog for language-specific wrappers).
|
||||
|
||||
### Changed
|
||||
- [**breaking**] Switch RVM binary serialization to postcard, bump the format to v4, and mark v1-3 loads as partial (recompile required).
|
||||
|
||||
## [0.9.1](https://github.com/microsoft/regorus/compare/regorus-v0.9.0...regorus-v0.9.1) - 2026-02-06
|
||||
|
||||
### Fixed
|
||||
- Release native C# handles reliably to avoid memory growth ([#571](https://github.com/microsoft/regorus/pull/571)).
|
||||
- Centralize C# handle gating with a short dispose wait and deferred release to avoid leaks while blocking new calls ([#571](https://github.com/microsoft/regorus/pull/571)).
|
||||
|
||||
### Added
|
||||
- Manual C# memory growth tests for both `using` and finalizer paths ([#571](https://github.com/microsoft/regorus/pull/571)).
|
||||
- C# test runner options for filtered tests, console logging, and skipping sample apps ([#571](https://github.com/microsoft/regorus/pull/571)).
|
||||
|
||||
## [0.5.0](https://github.com/microsoft/regorus/compare/regorus-v0.4.0...regorus-v0.5.0) - 2025-07-08
|
||||
|
||||
### Added
|
||||
|
||||
991
Cargo.lock
generated
991
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
69
Cargo.toml
69
Cargo.toml
@@ -8,12 +8,17 @@ members = [
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.5.0"
|
||||
version = "0.9.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
|
||||
|
||||
# Enable verification with Verus
|
||||
|
||||
[package.metadata.verus]
|
||||
verify = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
@@ -22,10 +27,10 @@ doctest = false
|
||||
[features]
|
||||
default = ["full-opa", "arc", "rvm"]
|
||||
|
||||
arc = ["scientific/arc"]
|
||||
arc = []
|
||||
ast = []
|
||||
azure_policy = ["dep:jsonschema", "arc", "dashmap"]
|
||||
azure-rbac = []
|
||||
azure_policy = ["dep:jsonschema", "dep:chrono", "dep:ipnet", "dep:icu_casemap", "dep:hashbrown", "arc", "dashmap"]
|
||||
azure-rbac = ["regex", "time", "net"]
|
||||
base64 = ["dep:data-encoding"]
|
||||
base64url = ["dep:data-encoding"]
|
||||
coverage = []
|
||||
@@ -39,9 +44,11 @@ net = ["dep:ipnet"]
|
||||
no_std = ["lazy_static/spin_no_std"]
|
||||
opa-runtime = []
|
||||
regex = ["dep:regex"]
|
||||
rvm = ["dep:bincode", "dep:indexmap"]
|
||||
cache = ["dep:lru"]
|
||||
rvm = ["dep:postcard", "dep:indexmap"]
|
||||
semver = ["dep:semver"]
|
||||
std = ["rand/std", "rand/std_rng", "serde_json/std", "msvc_spectre_libs" ]
|
||||
allocator-memory-limits = ["std", "mimalloc", "mimalloc/allocator-memory-limits"]
|
||||
std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot" ]
|
||||
time = ["dep:chrono", "dep:chrono-tz"]
|
||||
uuid = ["dep:uuid"]
|
||||
urlquery = ["dep:url"]
|
||||
@@ -55,10 +62,10 @@ full-opa = [
|
||||
"hex",
|
||||
"http",
|
||||
"jsonschema",
|
||||
"mimalloc",
|
||||
"net",
|
||||
"opa-runtime",
|
||||
"regex",
|
||||
"cache",
|
||||
"semver",
|
||||
"std",
|
||||
"time",
|
||||
@@ -94,47 +101,56 @@ opa-testutil = []
|
||||
rand = ["dep:rand"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.45", default-features = false }
|
||||
anyhow = { version = "1.0.102", default-features = false }
|
||||
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
|
||||
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
|
||||
hashbrown = { version = "0.16", default-features = false, features = ["default-hasher"], optional = true }
|
||||
lazy_static = { version = "1.4.0", default-features = false }
|
||||
thiserror = { version = "2.0", default-features = false }
|
||||
|
||||
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
|
||||
scientific = { version = "0.5.3", default-features = false }
|
||||
num-bigint = { version = "0.4", default-features = false }
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
parking_lot = { version = "0.12", optional = true }
|
||||
spin = { version = "0.10.0", default-features = false, features = ["mutex", "spin_mutex"] }
|
||||
|
||||
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
|
||||
regex = {version = "1.11.1", optional = true, default-features = false }
|
||||
regex = {version = "1.12.3", optional = true, default-features = false }
|
||||
semver = {version = "1.0.25", optional = true, default-features = false }
|
||||
url = { version = "2.5.4", optional = true }
|
||||
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.30.0", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.40", optional = true }
|
||||
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.45.0", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.44", optional = true }
|
||||
chrono-tz = { version = "0.10.1", optional = true }
|
||||
ipnet = { version = "2.11.0", optional = true, default-features = false }
|
||||
ipnet = { version = "2.12.0", optional = true, default-features = false }
|
||||
icu_casemap = { version = "2.1", optional = true, default-features = false, features = ["compiled_data"] }
|
||||
|
||||
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
|
||||
# Specify thread_rng for in order to use random_range
|
||||
rand = { version = "0.9.0", default-features = false, features = ["thread_rng"], optional = true }
|
||||
rand = { version = "0.10.0", default-features = false, features = ["thread_rng"], optional = true }
|
||||
|
||||
# Causes the project to link with the Spectre-mitigated CRT and libs.
|
||||
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
|
||||
dashmap = { version = "6.1", default-features = false, optional = true }
|
||||
mimalloc = { path = "mimalloc", optional = true }
|
||||
lru = { version = "0.16", default-features = false, optional = true }
|
||||
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.6", optional = true }
|
||||
|
||||
# rvm related deps
|
||||
indexmap = { version = "2.12.1", default-features = false, features = ["serde"], optional = true }
|
||||
bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"], optional = true }
|
||||
postcard = { version = "1.1.3", default-features = false, features = ["alloc"], optional = true }
|
||||
|
||||
# Use Verus for verification
|
||||
vstd = { version = "0.0.0-2026-03-17-2326" }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.45"
|
||||
anyhow = "1.0.102"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
prettydiff = { version = "0.9.0", default-features = false }
|
||||
serde_yaml = "0.9.16"
|
||||
test-generator = "0.3.1"
|
||||
walkdir = "2.3.2"
|
||||
criterion = { version = "0.7" }
|
||||
criterion = { version = "0.8" }
|
||||
|
||||
num_cpus = "1.16"
|
||||
|
||||
@@ -185,6 +201,16 @@ harness = false
|
||||
name = "aci_benchmark"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rvm_benchmark"
|
||||
harness = false
|
||||
required-features = ["rvm"]
|
||||
|
||||
[[bench]]
|
||||
name = "normalization_benchmark"
|
||||
harness = false
|
||||
required-features = ["azure_policy"]
|
||||
|
||||
[[example]]
|
||||
name="regorus"
|
||||
harness=false
|
||||
@@ -196,3 +222,6 @@ doctest=false
|
||||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(verus_keep_ghost)'] }
|
||||
|
||||
235
LICENSE
235
LICENSE
@@ -19,3 +19,238 @@
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
|
||||
The file src/builtins/time/diff.rs contains code derived from Go's `time`
|
||||
package, which carries the following license:
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Some files are licensed Apache-2.0 (LICENSE-2.0.txt).
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
16
README.md
16
README.md
@@ -3,7 +3,7 @@
|
||||
**Regorus** is
|
||||
|
||||
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
|
||||
interpreter written in Rust.
|
||||
interpreter written in Rust.
|
||||
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
|
||||
|
||||
Regorus is also
|
||||
@@ -274,6 +274,19 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
|
||||
Range (min … max): 43.8 ms … 46.7 ms 62 runs
|
||||
|
||||
```
|
||||
|
||||
## Contributor Workflow
|
||||
|
||||
Regorus uses a small companion CLI under the `xtask` package to keep CI and local development in sync.
|
||||
The commands mirror our GitHub Actions jobs, making it easy to dry-run CI steps before sending a pull request.
|
||||
|
||||
- Run the full release pipeline with `cargo xtask ci-release` and the debug checks with `cargo xtask ci-debug`.
|
||||
- Exercise language bindings through focused helpers such as `cargo xtask test-java --release --frozen` or `cargo xtask test-go`.
|
||||
- Use `cargo xtask test-musl --release --frozen` for the cross-compilation matrix and `cargo xtask test-no-std` for embedded targets.
|
||||
- Formatting (`cargo xtask fmt`) and linting (`cargo xtask clippy --sarif`) wrap the usual Cargo tooling while matching CI defaults.
|
||||
|
||||
The workflows in `.github/workflows` invoke the same commands, so keeping local runs green is usually enough to satisfy the checks enforced on `main`.
|
||||
|
||||
## OPA Conformance
|
||||
|
||||
Regorus has been verified to be compliant with [OPA v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.0)
|
||||
@@ -304,7 +317,6 @@ The following test suites don't pass fully due to missing builtins:
|
||||
- `jwtverifyhs512`
|
||||
- `jwtverifyrsa`
|
||||
- `netcidrcontainsmatches`
|
||||
- `netcidrexpand`
|
||||
- `netcidrintersects`
|
||||
- `netcidrmerge`
|
||||
- `netcidroverlap`
|
||||
|
||||
560
benches/normalization_benchmark.rs
Normal file
560
benches/normalization_benchmark.rs
Normal file
@@ -0,0 +1,560 @@
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use regorus::languages::azure_policy::aliases::{denormalizer, normalizer, AliasRegistry};
|
||||
use regorus::Value;
|
||||
use serde_json::json;
|
||||
|
||||
// ─── Alias catalog (reused across benchmarks) ───────────────────────────────
|
||||
|
||||
const ALIASES_JSON: &str = r#"[
|
||||
{
|
||||
"namespace": "Microsoft.Network",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "networkSecurityGroups",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
|
||||
"defaultPath": "properties.securityRules[*].properties.access",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
|
||||
"defaultPath": "properties.securityRules[*].properties.priority",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
|
||||
"defaultPath": "properties.securityRules[*].properties.direction",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
|
||||
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
|
||||
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||
"defaultPath": "properties.securityRules[*].name",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules[*].protocol",
|
||||
"defaultPath": "properties.defaultSecurityRules[*].properties.protocol",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "storageAccounts",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/accessTier",
|
||||
"defaultPath": "properties.accessTier",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
|
||||
"defaultPath": "properties.isHnsEnabled",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
|
||||
"defaultPath": "properties.minimumTlsVersion",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess",
|
||||
"defaultPath": "properties.allowBlobPublicAccess",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Storage/storageAccounts/sku.name",
|
||||
"defaultPath": "sku.name",
|
||||
"paths": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
fn build_registry() -> AliasRegistry {
|
||||
let mut reg = AliasRegistry::new();
|
||||
reg.load_from_json(ALIASES_JSON).unwrap();
|
||||
reg
|
||||
}
|
||||
|
||||
/// Convert a serde_json::Value to regorus::Value.
|
||||
fn to_regorus(v: serde_json::Value) -> Value {
|
||||
Value::from(v)
|
||||
}
|
||||
|
||||
// ─── Input resources ────────────────────────────────────────────────────────
|
||||
|
||||
fn simple_storage_resource() -> Value {
|
||||
to_regorus(json!({
|
||||
"name": "myStorageAccount",
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"location": "westus2",
|
||||
"kind": "StorageV2",
|
||||
"sku": { "name": "Standard_LRS", "tier": "Standard" },
|
||||
"tags": { "environment": "production", "team": "platform" },
|
||||
"properties": {
|
||||
"supportsHttpsTrafficOnly": true,
|
||||
"accessTier": "Hot",
|
||||
"isHnsEnabled": false,
|
||||
"minimumTlsVersion": "TLS1_2",
|
||||
"allowBlobPublicAccess": false
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn nsg_resource(rule_count: usize) -> Value {
|
||||
let rules: Vec<serde_json::Value> = (0..rule_count)
|
||||
.map(|i| {
|
||||
json!({
|
||||
"name": format!("rule-{}", i),
|
||||
"properties": {
|
||||
"protocol": "Tcp",
|
||||
"access": if i % 2 == 0 { "Allow" } else { "Deny" },
|
||||
"priority": 100 + i,
|
||||
"direction": "Inbound",
|
||||
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
|
||||
"destinationPortRange": format!("{}", 80 + i)
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
to_regorus(json!({
|
||||
"name": "myNsg",
|
||||
"type": "Microsoft.Network/networkSecurityGroups",
|
||||
"location": "eastus",
|
||||
"properties": {
|
||||
"securityRules": rules
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// ─── Benchmarks ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn bench_normalize_simple(c: &mut Criterion) {
|
||||
let registry = build_registry();
|
||||
let resource = simple_storage_resource();
|
||||
|
||||
c.bench_function("normalize/simple_storage", |b| {
|
||||
b.iter(|| normalizer::normalize(black_box(&resource), Some(®istry), None))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_normalize_no_aliases(c: &mut Criterion) {
|
||||
let resource = simple_storage_resource();
|
||||
|
||||
c.bench_function("normalize/simple_no_aliases", |b| {
|
||||
b.iter(|| normalizer::normalize(black_box(&resource), None, None))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_normalize_nsg_scaling(c: &mut Criterion) {
|
||||
let registry = build_registry();
|
||||
let mut group = c.benchmark_group("normalize/nsg_rules");
|
||||
|
||||
for rule_count in [5, 20, 100] {
|
||||
let resource = nsg_resource(rule_count);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(rule_count),
|
||||
&resource,
|
||||
|b, res| b.iter(|| normalizer::normalize(black_box(res), Some(®istry), None)),
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_denormalize_simple(c: &mut Criterion) {
|
||||
let registry = build_registry();
|
||||
let resource = simple_storage_resource();
|
||||
let normalized = normalizer::normalize(&resource, Some(®istry), None);
|
||||
|
||||
c.bench_function("denormalize/simple_storage", |b| {
|
||||
b.iter(|| denormalizer::denormalize(black_box(&normalized), Some(®istry), None))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_denormalize_nsg_scaling(c: &mut Criterion) {
|
||||
let registry = build_registry();
|
||||
let mut group = c.benchmark_group("denormalize/nsg_rules");
|
||||
|
||||
for rule_count in [5, 20, 100] {
|
||||
let resource = nsg_resource(rule_count);
|
||||
let normalized = normalizer::normalize(&resource, Some(®istry), None);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(rule_count),
|
||||
&normalized,
|
||||
|b, norm| b.iter(|| denormalizer::denormalize(black_box(norm), Some(®istry), None)),
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_round_trip(c: &mut Criterion) {
|
||||
let registry = build_registry();
|
||||
let resource = nsg_resource(20);
|
||||
|
||||
c.bench_function("round_trip/nsg_20_rules", |b| {
|
||||
b.iter(|| {
|
||||
let n = normalizer::normalize(black_box(&resource), Some(®istry), None);
|
||||
denormalizer::denormalize(&n, Some(®istry), None)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_normalize_and_wrap(c: &mut Criterion) {
|
||||
let registry = build_registry();
|
||||
let resource = nsg_resource(20);
|
||||
let context = to_regorus(json!({"resourceGroup": {"name": "rg1"}}));
|
||||
let parameters = to_regorus(json!({"env": "prod"}));
|
||||
|
||||
c.bench_function("normalize_and_wrap/nsg_20_rules", |b| {
|
||||
b.iter(|| {
|
||||
registry.normalize_and_wrap(
|
||||
black_box(&resource),
|
||||
None,
|
||||
Some(context.clone()),
|
||||
Some(parameters.clone()),
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_registry_load(c: &mut Criterion) {
|
||||
c.bench_function("registry/load_from_json", |b| {
|
||||
b.iter(|| {
|
||||
let mut reg = AliasRegistry::new();
|
||||
reg.load_from_json(black_box(ALIASES_JSON)).unwrap();
|
||||
reg
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Large-payload benchmarks ───────────────────────────────────────────────
|
||||
//
|
||||
// These stress the hot paths identified in the performance analysis:
|
||||
// - Nested set helpers (alias-heavy catalog with deep properties)
|
||||
// - Array element remap/cleanup/rewrap (large sub-resource arrays)
|
||||
// - Scalar denormalization lookups (many aliases × many fields)
|
||||
|
||||
/// Build a large alias catalog with `n` scalar aliases for storage accounts.
|
||||
/// Each alias maps to a nested `properties.section_i.field_j` path, creating
|
||||
/// deep nested-set workloads.
|
||||
fn large_alias_catalog(n: usize) -> String {
|
||||
let mut aliases = Vec::new();
|
||||
for i in 0..n {
|
||||
let section = i / 10;
|
||||
let field = i % 10;
|
||||
aliases.push(format!(
|
||||
r#"{{
|
||||
"name": "Microsoft.Storage/storageAccounts/section{section}Field{field}",
|
||||
"defaultPath": "properties.section{section}.field{field}",
|
||||
"paths": []
|
||||
}}"#,
|
||||
));
|
||||
}
|
||||
format!(
|
||||
r#"[{{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [{{
|
||||
"resourceType": "storageAccounts",
|
||||
"aliases": [{aliases}]
|
||||
}}]
|
||||
}}]"#,
|
||||
aliases = aliases.join(",")
|
||||
)
|
||||
}
|
||||
|
||||
/// Build a storage account resource whose `properties` contain nested sections
|
||||
/// matching the large alias catalog.
|
||||
fn large_storage_resource(alias_count: usize) -> Value {
|
||||
let mut sections = serde_json::Map::new();
|
||||
for i in 0..alias_count {
|
||||
let section = i / 10;
|
||||
let field = i % 10;
|
||||
let section_key = format!("section{section}");
|
||||
let section_obj = sections
|
||||
.entry(section_key)
|
||||
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
|
||||
if let serde_json::Value::Object(m) = section_obj {
|
||||
m.insert(format!("field{field}"), serde_json::Value::from(i));
|
||||
}
|
||||
}
|
||||
Value::from(json!({
|
||||
"name": "bigStorage",
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"location": "westus2",
|
||||
"properties": sections
|
||||
}))
|
||||
}
|
||||
|
||||
fn bench_normalize_large_catalog(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("normalize/large_catalog");
|
||||
for alias_count in [50, 200] {
|
||||
let catalog_json = large_alias_catalog(alias_count);
|
||||
let mut reg = AliasRegistry::new();
|
||||
reg.load_from_json(&catalog_json).unwrap();
|
||||
let resource = large_storage_resource(alias_count);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(alias_count),
|
||||
&(reg, resource),
|
||||
|b, (reg, res)| b.iter(|| normalizer::normalize(black_box(res), Some(reg), None)),
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_denormalize_large_catalog(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("denormalize/large_catalog");
|
||||
for alias_count in [50, 200] {
|
||||
let catalog_json = large_alias_catalog(alias_count);
|
||||
let mut reg = AliasRegistry::new();
|
||||
reg.load_from_json(&catalog_json).unwrap();
|
||||
let resource = large_storage_resource(alias_count);
|
||||
let normalized = normalizer::normalize(&resource, Some(®), None);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(alias_count),
|
||||
&(reg, normalized),
|
||||
|b, (reg, norm)| b.iter(|| denormalizer::denormalize(black_box(norm), Some(reg), None)),
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_nsg_large_subarrays(c: &mut Criterion) {
|
||||
let registry = build_registry();
|
||||
let mut group = c.benchmark_group("round_trip/nsg_sub_resource");
|
||||
for rule_count in [50, 200, 500] {
|
||||
let resource = nsg_resource(rule_count);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(rule_count),
|
||||
&resource,
|
||||
|b, res| {
|
||||
b.iter(|| {
|
||||
let n = normalizer::normalize(black_box(res), Some(®istry), None);
|
||||
denormalizer::denormalize(&n, Some(®istry), None)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ─── Versioned-path benchmarks ──────────────────────────────────────────────
|
||||
//
|
||||
// Exercise the precomputed versioned-path aggregates by building a catalog
|
||||
// where wildcard (array) aliases have version-specific paths that differ from
|
||||
// the default, then running normalize/denormalize with an explicit api_version.
|
||||
|
||||
/// NSG-like alias catalog where wildcard aliases have versioned paths that
|
||||
/// differ from the default. This forces the normalize/denormalize path through
|
||||
/// the versioned aggregate lookup rather than the default-aggregate fast path.
|
||||
const VERSIONED_ALIASES_JSON: &str = r#"[
|
||||
{
|
||||
"namespace": "Microsoft.Network",
|
||||
"resourceTypes": [
|
||||
{
|
||||
"resourceType": "networkSecurityGroups",
|
||||
"aliases": [
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||
"paths": [
|
||||
{ "path": "properties.securityRules[*].properties.transportProtocol", "apiVersions": ["2020-01-01"] },
|
||||
{ "path": "properties.securityRules[*].properties.protocol", "apiVersions": ["2022-01-01"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
|
||||
"defaultPath": "properties.securityRules[*].properties.access",
|
||||
"paths": [
|
||||
{ "path": "properties.securityRules[*].properties.accessLevel", "apiVersions": ["2020-01-01"] },
|
||||
{ "path": "properties.securityRules[*].properties.access", "apiVersions": ["2022-01-01"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
|
||||
"defaultPath": "properties.securityRules[*].properties.priority",
|
||||
"paths": [
|
||||
{ "path": "properties.securityRules[*].properties.rulePriority", "apiVersions": ["2020-01-01"] },
|
||||
{ "path": "properties.securityRules[*].properties.priority", "apiVersions": ["2022-01-01"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
|
||||
"defaultPath": "properties.securityRules[*].properties.direction",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
|
||||
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
|
||||
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||
"defaultPath": "properties.securityRules[*].name",
|
||||
"paths": []
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.Network/networkSecurityGroups/provisioningState",
|
||||
"defaultPath": "properties.provisioningState",
|
||||
"paths": [
|
||||
{ "path": "properties.state", "apiVersions": ["2020-01-01"] },
|
||||
{ "path": "properties.provisioningState", "apiVersions": ["2022-01-01"] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
fn build_versioned_registry() -> AliasRegistry {
|
||||
let mut reg = AliasRegistry::new();
|
||||
reg.load_from_json(VERSIONED_ALIASES_JSON).unwrap();
|
||||
reg
|
||||
}
|
||||
|
||||
/// Build an NSG resource for versioned-path benchmarks.
|
||||
/// Uses the 2020-01-01 field names (`transportProtocol`, `accessLevel`,
|
||||
/// `rulePriority`) so that versioned path resolution actually differs from
|
||||
/// the default.
|
||||
fn nsg_versioned_resource(rule_count: usize) -> Value {
|
||||
let rules: Vec<serde_json::Value> = (0..rule_count)
|
||||
.map(|i| {
|
||||
json!({
|
||||
"name": format!("rule-{}", i),
|
||||
"properties": {
|
||||
"transportProtocol": "Tcp",
|
||||
"accessLevel": if i % 2 == 0 { "Allow" } else { "Deny" },
|
||||
"rulePriority": 100 + i,
|
||||
"direction": "Inbound",
|
||||
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
|
||||
"destinationPortRange": format!("{}", 80 + i)
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
to_regorus(json!({
|
||||
"name": "myNsg",
|
||||
"type": "Microsoft.Network/networkSecurityGroups",
|
||||
"location": "eastus",
|
||||
"properties": {
|
||||
"state": "Succeeded",
|
||||
"securityRules": rules
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn bench_normalize_versioned(c: &mut Criterion) {
|
||||
let registry = build_versioned_registry();
|
||||
let mut group = c.benchmark_group("normalize_versioned/nsg_rules");
|
||||
|
||||
for rule_count in [5, 20, 100] {
|
||||
let resource = nsg_versioned_resource(rule_count);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(rule_count),
|
||||
&resource,
|
||||
|b, res| {
|
||||
b.iter(|| {
|
||||
normalizer::normalize(black_box(res), Some(®istry), Some("2020-01-01"))
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_denormalize_versioned(c: &mut Criterion) {
|
||||
let registry = build_versioned_registry();
|
||||
let mut group = c.benchmark_group("denormalize_versioned/nsg_rules");
|
||||
|
||||
for rule_count in [5, 20, 100] {
|
||||
let resource = nsg_versioned_resource(rule_count);
|
||||
let normalized = normalizer::normalize(&resource, Some(®istry), Some("2020-01-01"));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(rule_count),
|
||||
&normalized,
|
||||
|b, norm| {
|
||||
b.iter(|| {
|
||||
denormalizer::denormalize(black_box(norm), Some(®istry), Some("2020-01-01"))
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_round_trip_versioned(c: &mut Criterion) {
|
||||
let registry = build_versioned_registry();
|
||||
let mut group = c.benchmark_group("round_trip_versioned/nsg_rules");
|
||||
|
||||
for rule_count in [20, 100] {
|
||||
let resource = nsg_versioned_resource(rule_count);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(rule_count),
|
||||
&resource,
|
||||
|b, res| {
|
||||
b.iter(|| {
|
||||
let n =
|
||||
normalizer::normalize(black_box(res), Some(®istry), Some("2020-01-01"));
|
||||
denormalizer::denormalize(&n, Some(®istry), Some("2020-01-01"))
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
normalization_benches,
|
||||
bench_normalize_simple,
|
||||
bench_normalize_no_aliases,
|
||||
bench_normalize_nsg_scaling,
|
||||
bench_denormalize_simple,
|
||||
bench_denormalize_nsg_scaling,
|
||||
bench_round_trip,
|
||||
bench_normalize_and_wrap,
|
||||
bench_registry_load,
|
||||
bench_normalize_large_catalog,
|
||||
bench_denormalize_large_catalog,
|
||||
bench_nsg_large_subarrays,
|
||||
bench_normalize_versioned,
|
||||
bench_denormalize_versioned,
|
||||
bench_round_trip_versioned,
|
||||
);
|
||||
criterion_main!(normalization_benches);
|
||||
680
benches/rvm_benchmark.rs
Normal file
680
benches/rvm_benchmark.rs
Normal file
@@ -0,0 +1,680 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Comprehensive RVM benchmarks covering all aspects of the Rego Virtual Machine.
|
||||
//!
|
||||
//! # Policy families
|
||||
//!
|
||||
//! | Family | Source | Policies | Inputs/policy |
|
||||
//! |------------|-------------------------------|----------|---------------|
|
||||
//! | Synthetic | `benches/evaluation/test_data`| 9 | 3 each |
|
||||
//! | ACI | `tests/aci` | 9 | 1 each |
|
||||
//!
|
||||
//! # Benchmark groups
|
||||
//!
|
||||
//! | Group | What it measures |
|
||||
//! |--------------------------|-------------------------------------------------------|
|
||||
//! | `cold/{case}/{config}` | Cold: new VM + load + data + input + execute |
|
||||
//! | `hot/{case}/{config}` | Hot: set_input + execute (VM reused across iters) |
|
||||
//! | `compilation` | Rego CompiledPolicy → RVM Program |
|
||||
//! | `serialization` | Program binary serialize / deserialize roundtrip |
|
||||
//! | `startup` | Isolated VM creation & setup overhead |
|
||||
//! | `stats` | Instruction/literal counts (reported as throughput) |
|
||||
//! | `end_to_end` | Full roundtrip: compile → serialize → deserialize → eval |
|
||||
//!
|
||||
//! # Running subsets
|
||||
//!
|
||||
//! ```sh
|
||||
//! cargo bench --bench rvm_benchmark # everything
|
||||
//! cargo bench --bench rvm_benchmark -- cold # all cold eval
|
||||
//! cargo bench --bench rvm_benchmark -- hot # all hot eval
|
||||
//! cargo bench --bench rvm_benchmark -- regular_with_limits # one config across cases
|
||||
//! cargo bench --bench rvm_benchmark -- cold/aci/ # all ACI cold benchmarks
|
||||
//! cargo bench --bench rvm_benchmark -- rbac # one policy family
|
||||
//! cargo bench --bench rvm_benchmark -- compilation # compilation only
|
||||
//! cargo bench --bench rvm_benchmark -- serialization # serialization only
|
||||
//! cargo bench --bench rvm_benchmark -- startup # startup overhead
|
||||
//! ```
|
||||
|
||||
use std::hint::black_box;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use regorus::languages::rego::compiler::Compiler;
|
||||
use regorus::rvm::program::Program;
|
||||
use regorus::rvm::vm::{ExecutionMode, RegoVM};
|
||||
use regorus::utils::limits::ExecutionTimerConfig;
|
||||
use regorus::{Engine, Rc, Value};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Limit constants – generous ceilings that still exercise the limit-checking
|
||||
// hot path (memory_check, execution_timer_tick, instruction-limit compare).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
const MEMORY_LIMIT_BYTES: u64 = 256 * 1024 * 1024;
|
||||
const TIME_LIMIT: Duration = Duration::from_secs(30);
|
||||
const TIMER_CHECK_INTERVAL: NonZeroU32 = NonZeroU32::new(16).unwrap();
|
||||
const INSTRUCTION_LIMIT: usize = 10_000_000;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct EvalConfig {
|
||||
name: &'static str,
|
||||
mode: ExecutionMode,
|
||||
limits: bool,
|
||||
}
|
||||
|
||||
const EVAL_CONFIGS: [EvalConfig; 4] = [
|
||||
EvalConfig {
|
||||
name: "regular_no_limits",
|
||||
mode: ExecutionMode::RunToCompletion,
|
||||
limits: false,
|
||||
},
|
||||
EvalConfig {
|
||||
name: "regular_with_limits",
|
||||
mode: ExecutionMode::RunToCompletion,
|
||||
limits: true,
|
||||
},
|
||||
EvalConfig {
|
||||
name: "suspendable_no_limits",
|
||||
mode: ExecutionMode::Suspendable,
|
||||
limits: false,
|
||||
},
|
||||
EvalConfig {
|
||||
name: "suspendable_with_limits",
|
||||
mode: ExecutionMode::Suspendable,
|
||||
limits: true,
|
||||
},
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Data types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A compiled benchmark program ready for RVM execution.
|
||||
struct BenchmarkProgram {
|
||||
/// Human-readable name (e.g. "rbac_policy" or "aci/create_container").
|
||||
name: String,
|
||||
/// Pre-compiled RVM program.
|
||||
program: Arc<Program>,
|
||||
/// Compiled policy (kept for compilation benchmarks).
|
||||
compiled_policy: regorus::CompiledPolicy,
|
||||
/// Entry-point rule path.
|
||||
entry_point: String,
|
||||
/// Data object (Some for policies that require external data like ACI).
|
||||
data: Option<Value>,
|
||||
/// Named inputs for this policy.
|
||||
inputs: Vec<(String, Value)>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ACI YAML types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct AciTestCase {
|
||||
note: String,
|
||||
data: Value,
|
||||
input: Value,
|
||||
modules: Vec<String>,
|
||||
query: String,
|
||||
want_result: Value,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct AciYamlTest {
|
||||
cases: Vec<AciTestCase>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Synthetic policy loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Policy ↔ input file mapping for synthetic policies.
|
||||
const SYNTHETIC_POLICIES: &[(&str, &str, &[&str])] = &[
|
||||
(
|
||||
"rbac_policy",
|
||||
"rbac_policy.rego",
|
||||
&["rbac_input.json", "rbac_input2.json", "rbac_input3.json"],
|
||||
),
|
||||
(
|
||||
"api_access",
|
||||
"api_access_policy.rego",
|
||||
&[
|
||||
"api_access_input.json",
|
||||
"api_access_input2.json",
|
||||
"api_access_input3.json",
|
||||
],
|
||||
),
|
||||
(
|
||||
"data_sensitivity",
|
||||
"data_sensitivity_policy.rego",
|
||||
&[
|
||||
"data_sensitivity_input.json",
|
||||
"data_sensitivity_input2.json",
|
||||
"data_sensitivity_input3.json",
|
||||
],
|
||||
),
|
||||
(
|
||||
"time_based",
|
||||
"time_based_policy.rego",
|
||||
&[
|
||||
"time_based_input.json",
|
||||
"time_based_input2.json",
|
||||
"time_based_input3.json",
|
||||
],
|
||||
),
|
||||
(
|
||||
"data_processing",
|
||||
"data_processing_policy.rego",
|
||||
&[
|
||||
"data_processing_input.json",
|
||||
"data_processing_input2.json",
|
||||
"data_processing_input3.json",
|
||||
],
|
||||
),
|
||||
(
|
||||
"azure_vm",
|
||||
"azure_vm_policy.rego",
|
||||
&[
|
||||
"azure_vm_input.json",
|
||||
"azure_vm_input2.json",
|
||||
"azure_vm_input3.json",
|
||||
],
|
||||
),
|
||||
(
|
||||
"azure_storage",
|
||||
"azure_storage_policy.rego",
|
||||
&[
|
||||
"azure_storage_input.json",
|
||||
"azure_storage_input2.json",
|
||||
"azure_storage_input3.json",
|
||||
],
|
||||
),
|
||||
(
|
||||
"azure_keyvault",
|
||||
"azure_keyvault_policy.rego",
|
||||
&[
|
||||
"azure_keyvault_input.json",
|
||||
"azure_keyvault_input2.json",
|
||||
"azure_keyvault_input3.json",
|
||||
],
|
||||
),
|
||||
(
|
||||
"azure_nsg",
|
||||
"azure_nsg_policy.rego",
|
||||
&[
|
||||
"azure_nsg_input.json",
|
||||
"azure_nsg_input2.json",
|
||||
"azure_nsg_input3.json",
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
/// Compile synthetic Rego policies into RVM programs.
|
||||
fn compile_synthetic_programs() -> Vec<BenchmarkProgram> {
|
||||
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("benches")
|
||||
.join("evaluation")
|
||||
.join("test_data");
|
||||
|
||||
let entry_point = "data.bench.allow";
|
||||
let entry_point_rc: Rc<str> = entry_point.into();
|
||||
|
||||
SYNTHETIC_POLICIES
|
||||
.iter()
|
||||
.map(|(name, policy_file, input_files)| {
|
||||
let policy_path = base_dir.join("policies").join(policy_file);
|
||||
let policy_content = std::fs::read_to_string(&policy_path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {policy_path:?}: {e}"));
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine
|
||||
.add_policy("policy.rego".to_string(), policy_content)
|
||||
.expect("failed to add policy");
|
||||
|
||||
let compiled_policy = engine
|
||||
.compile_with_entrypoint(&entry_point_rc)
|
||||
.expect("failed to compile policy");
|
||||
|
||||
let program = Compiler::compile_from_policy(&compiled_policy, &[entry_point])
|
||||
.expect("failed to compile to RVM program");
|
||||
|
||||
let inputs: Vec<(String, Value)> = input_files
|
||||
.iter()
|
||||
.map(|input_file| {
|
||||
let input_path = base_dir.join("inputs").join(input_file);
|
||||
let json = std::fs::read_to_string(&input_path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {input_path:?}: {e}"));
|
||||
let value = Value::from_json_str(&json).expect("failed to parse input JSON");
|
||||
let display = input_file.trim_end_matches(".json").to_string();
|
||||
(display, value)
|
||||
})
|
||||
.collect();
|
||||
|
||||
BenchmarkProgram {
|
||||
name: name.to_string(),
|
||||
program,
|
||||
compiled_policy,
|
||||
entry_point: entry_point.to_string(),
|
||||
data: None,
|
||||
inputs,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ACI policy loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Load all ACI test cases from YAML files.
|
||||
fn load_aci_cases(dir: &Path) -> Vec<AciTestCase> {
|
||||
let mut cases = Vec::new();
|
||||
for entry in WalkDir::new(dir)
|
||||
.sort_by_file_name()
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
{
|
||||
let path = entry.path();
|
||||
if !path.to_string_lossy().ends_with(".yaml") {
|
||||
continue;
|
||||
}
|
||||
let yaml = std::fs::read(path).expect("failed to read yaml");
|
||||
let yaml = String::from_utf8_lossy(&yaml);
|
||||
let test: AciYamlTest = serde_yaml::from_str(&yaml).expect("failed to deserialize yaml");
|
||||
cases.extend(test.cases);
|
||||
}
|
||||
cases
|
||||
}
|
||||
|
||||
/// Build an Engine with policies loaded for a given ACI test case.
|
||||
fn build_aci_engine(dir: &Path, case: &AciTestCase) -> Engine {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_rego_v0(true);
|
||||
engine
|
||||
.add_data(case.data.clone())
|
||||
.expect("failed to add data");
|
||||
engine.set_input(case.input.clone());
|
||||
for (idx, rego) in case.modules.iter().enumerate() {
|
||||
if rego.ends_with(".rego") {
|
||||
engine
|
||||
.add_policy_from_file(dir.join(rego).to_str().expect("invalid path"))
|
||||
.expect("failed to add policy");
|
||||
} else {
|
||||
engine
|
||||
.add_policy(format!("rego{idx}.rego"), rego.clone())
|
||||
.expect("failed to add policy");
|
||||
}
|
||||
}
|
||||
engine
|
||||
}
|
||||
|
||||
/// Compile ACI test cases into RVM programs.
|
||||
fn compile_aci_programs() -> Vec<BenchmarkProgram> {
|
||||
let dir = Path::new("tests/aci");
|
||||
load_aci_cases(dir)
|
||||
.into_iter()
|
||||
.map(|case| {
|
||||
let mut engine = build_aci_engine(dir, &case);
|
||||
let rule = case.query.replace("=x", "");
|
||||
let rule_rc: Rc<str> = rule.clone().into();
|
||||
let compiled_policy = engine
|
||||
.compile_with_entrypoint(&rule_rc)
|
||||
.expect("failed to compile");
|
||||
let program = Compiler::compile_from_policy(&compiled_policy, &[rule.as_str()])
|
||||
.expect("failed to compile to RVM");
|
||||
|
||||
BenchmarkProgram {
|
||||
name: format!("aci/{}", case.note),
|
||||
program,
|
||||
compiled_policy,
|
||||
entry_point: rule,
|
||||
data: Some(case.data),
|
||||
inputs: vec![("input".to_string(), case.input)],
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compile all policies
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Compile all policies (synthetic + ACI) into RVM programs.
|
||||
fn compile_all_programs() -> Vec<BenchmarkProgram> {
|
||||
let mut programs = compile_synthetic_programs();
|
||||
programs.extend(compile_aci_programs());
|
||||
programs
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Limit helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Apply or remove production-style limits based on a boolean flag.
|
||||
fn configure_limits(vm: &mut RegoVM, limits: bool) {
|
||||
if limits {
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
regorus::set_global_memory_limit(Some(MEMORY_LIMIT_BYTES));
|
||||
vm.set_execution_timer_config(Some(ExecutionTimerConfig {
|
||||
limit: TIME_LIMIT,
|
||||
check_interval: TIMER_CHECK_INTERVAL,
|
||||
}));
|
||||
vm.set_max_instructions(INSTRUCTION_LIMIT);
|
||||
} else {
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
regorus::set_global_memory_limit(None);
|
||||
vm.set_execution_timer_config(None);
|
||||
vm.set_max_instructions(usize::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cold evaluation — new VM per iteration (full setup + execute)
|
||||
//
|
||||
// Benchmarks are registered case-first so each workload is shown with all
|
||||
// config variants adjacent to one another, making per-case comparisons easier.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_cold(c: &mut Criterion) {
|
||||
let programs = compile_all_programs();
|
||||
let mut group = c.benchmark_group("cold");
|
||||
|
||||
for bp in &programs {
|
||||
for (input_name, input_value) in &bp.inputs {
|
||||
let case_id = if bp.inputs.len() == 1 {
|
||||
bp.name.clone()
|
||||
} else {
|
||||
format!("{}/{}", bp.name, input_name)
|
||||
};
|
||||
let program = bp.program.clone();
|
||||
let data = bp.data.clone();
|
||||
let input = input_value.clone();
|
||||
|
||||
for config in EVAL_CONFIGS {
|
||||
group.bench_function(BenchmarkId::new(&case_id, config.name), |b| {
|
||||
b.iter(|| {
|
||||
let mut vm = RegoVM::new();
|
||||
vm.set_execution_mode(config.mode);
|
||||
vm.load_program(black_box(program.clone()));
|
||||
if let Some(ref d) = data {
|
||||
vm.set_data(black_box(d.clone())).unwrap();
|
||||
}
|
||||
vm.set_input(black_box(input.clone()));
|
||||
configure_limits(&mut vm, config.limits);
|
||||
black_box(vm.execute().unwrap())
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hot evaluation — VM reused across iterations
|
||||
//
|
||||
// The VM is created once with program, data, mode, and limits. Each
|
||||
// iteration only calls set_input + execute, measuring pure execution
|
||||
// overhead with minimal setup. A warm-up execution fills the register
|
||||
// window pool so all iterations benefit from pooled allocations.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_hot(c: &mut Criterion) {
|
||||
let programs = compile_all_programs();
|
||||
let mut group = c.benchmark_group("hot");
|
||||
|
||||
for bp in &programs {
|
||||
let program = bp.program.clone();
|
||||
let data = bp.data.clone();
|
||||
let inputs: Vec<Value> = bp.inputs.iter().map(|(_, v)| v.clone()).collect();
|
||||
let num_inputs = inputs.len();
|
||||
|
||||
for config in EVAL_CONFIGS {
|
||||
group.bench_function(BenchmarkId::new(&bp.name, config.name), |b| {
|
||||
let mut vm = RegoVM::new();
|
||||
vm.set_execution_mode(config.mode);
|
||||
vm.load_program(program.clone());
|
||||
if let Some(ref d) = data {
|
||||
vm.set_data(d.clone()).unwrap();
|
||||
}
|
||||
configure_limits(&mut vm, config.limits);
|
||||
|
||||
// Warm up: fill register window pools, caches, etc.
|
||||
vm.set_input(inputs[0].clone());
|
||||
vm.execute().expect("warm-up failed");
|
||||
|
||||
let mut i = 0usize;
|
||||
b.iter(|| {
|
||||
let input = &inputs[i % num_inputs];
|
||||
vm.set_input(black_box(input.clone()));
|
||||
black_box(vm.execute().unwrap());
|
||||
i += 1;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compilation — Rego CompiledPolicy → RVM Program
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_compilation(c: &mut Criterion) {
|
||||
let programs = compile_all_programs();
|
||||
let mut group = c.benchmark_group("compilation");
|
||||
|
||||
for bp in &programs {
|
||||
let entry_point: &str = &bp.entry_point;
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("rego_to_rvm", &bp.name),
|
||||
&bp.compiled_policy,
|
||||
|b, compiled_policy| {
|
||||
b.iter(|| {
|
||||
Compiler::compile_from_policy(
|
||||
black_box(compiled_policy),
|
||||
black_box(&[entry_point]),
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serialization — binary serialize / deserialize roundtrip
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_serialization(c: &mut Criterion) {
|
||||
let programs = compile_all_programs();
|
||||
let mut group = c.benchmark_group("serialization");
|
||||
|
||||
for bp in &programs {
|
||||
let program = &bp.program;
|
||||
let serialized = program
|
||||
.serialize_binary()
|
||||
.expect("failed to serialize program");
|
||||
let byte_len = serialized.len() as u64;
|
||||
|
||||
group.throughput(Throughput::Bytes(byte_len));
|
||||
group.bench_function(BenchmarkId::new("serialize", &bp.name), |b| {
|
||||
b.iter(|| black_box(program.serialize_binary().unwrap()))
|
||||
});
|
||||
|
||||
group.throughput(Throughput::Bytes(byte_len));
|
||||
group.bench_function(BenchmarkId::new("deserialize", &bp.name), |b| {
|
||||
b.iter(|| black_box(Program::deserialize_binary(black_box(&serialized)).unwrap()))
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Startup — isolated VM creation & setup overhead
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_startup(c: &mut Criterion) {
|
||||
let programs = compile_all_programs();
|
||||
let mut group = c.benchmark_group("startup");
|
||||
|
||||
// Use the first program as representative for startup overhead.
|
||||
let bp = &programs[0];
|
||||
let program = bp.program.clone();
|
||||
let input = bp.inputs[0].1.clone();
|
||||
|
||||
// Bare VM creation
|
||||
group.bench_function("new", |b| b.iter(|| black_box(RegoVM::new())));
|
||||
|
||||
// load_program (Arc clone + internal setup)
|
||||
group.bench_function("load_program", |b| {
|
||||
b.iter(|| {
|
||||
let mut vm = RegoVM::new();
|
||||
vm.load_program(black_box(program.clone()));
|
||||
black_box(&vm);
|
||||
})
|
||||
});
|
||||
|
||||
// set_input
|
||||
group.bench_function("set_input", |b| {
|
||||
let mut vm = RegoVM::new();
|
||||
vm.load_program(program.clone());
|
||||
b.iter(|| {
|
||||
vm.set_input(black_box(input.clone()));
|
||||
})
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stats — instruction / literal counts (reported as throughput)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_stats(c: &mut Criterion) {
|
||||
let programs = compile_all_programs();
|
||||
|
||||
eprintln!();
|
||||
eprintln!(
|
||||
"{:<30} {:>8} {:>8} {:>8} {:>10}",
|
||||
"program", "instrs", "lits", "entries", "bytes"
|
||||
);
|
||||
eprintln!("{}", "-".repeat(70));
|
||||
|
||||
let mut group = c.benchmark_group("stats");
|
||||
for bp in &programs {
|
||||
let serialized = bp.program.serialize_binary().expect("serialize failed");
|
||||
let byte_len = serialized.len();
|
||||
let instr_count = bp.program.instructions.len();
|
||||
let lit_count = bp.program.literals.len();
|
||||
let entry_count = bp.program.entry_points.len();
|
||||
|
||||
eprintln!(
|
||||
"{:<30} {:>8} {:>8} {:>8} {:>10}",
|
||||
bp.name, instr_count, lit_count, entry_count, byte_len,
|
||||
);
|
||||
|
||||
group.throughput(Throughput::Elements(instr_count as u64));
|
||||
group.bench_function(BenchmarkId::new("serialize", &bp.name), |b| {
|
||||
b.iter(|| black_box(bp.program.serialize_binary().unwrap()))
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// End-to-end roundtrip (compile + serialize + deserialize + eval)
|
||||
//
|
||||
// Only runs for synthetic policies where we have direct access to rego
|
||||
// source files. ACI policies are loaded from YAML with module references
|
||||
// which makes the setup pipeline different.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn bench_end_to_end(c: &mut Criterion) {
|
||||
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("benches")
|
||||
.join("evaluation")
|
||||
.join("test_data");
|
||||
|
||||
let entry_point = "data.bench.allow";
|
||||
let entry_point_rc: Rc<str> = entry_point.into();
|
||||
|
||||
let mut group = c.benchmark_group("end_to_end");
|
||||
|
||||
for &(name, policy_file, input_files) in SYNTHETIC_POLICIES {
|
||||
let policy_path = base_dir.join("policies").join(policy_file);
|
||||
let policy_content = std::fs::read_to_string(&policy_path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {policy_path:?}: {e}"));
|
||||
|
||||
// Use just the first input for end-to-end
|
||||
let input_path = base_dir.join("inputs").join(input_files[0]);
|
||||
let input_json = std::fs::read_to_string(&input_path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {input_path:?}: {e}"));
|
||||
|
||||
group.bench_function(BenchmarkId::new("roundtrip", name), |b| {
|
||||
b.iter(|| {
|
||||
// 1. Engine + parse
|
||||
let mut engine = Engine::new();
|
||||
engine
|
||||
.add_policy("policy.rego".to_string(), policy_content.clone())
|
||||
.unwrap();
|
||||
|
||||
// 2. Compile to CompiledPolicy
|
||||
let compiled_policy = engine.compile_with_entrypoint(&entry_point_rc).unwrap();
|
||||
|
||||
// 3. Compile to RVM Program
|
||||
let program =
|
||||
Compiler::compile_from_policy(&compiled_policy, &[entry_point]).unwrap();
|
||||
|
||||
// 4. Serialize
|
||||
let bytes = program.serialize_binary().unwrap();
|
||||
|
||||
// 5. Deserialize
|
||||
let deserialized = Program::deserialize_binary(&bytes).unwrap();
|
||||
let program = match deserialized {
|
||||
regorus::rvm::program::DeserializationResult::Complete(p) => Arc::new(p),
|
||||
regorus::rvm::program::DeserializationResult::Partial(p) => {
|
||||
Arc::new(Program::compile_from_partial(p).unwrap())
|
||||
}
|
||||
};
|
||||
|
||||
// 6. Execute
|
||||
let mut vm = RegoVM::new();
|
||||
vm.load_program(program);
|
||||
let input = Value::from_json_str(&input_json).unwrap();
|
||||
vm.set_input(input);
|
||||
black_box(vm.execute().unwrap());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Criterion groups — organised for selective runs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
criterion_group!(cold_benches, bench_cold);
|
||||
|
||||
criterion_group!(hot_benches, bench_hot);
|
||||
|
||||
criterion_group!(
|
||||
misc_benches,
|
||||
bench_compilation,
|
||||
bench_serialization,
|
||||
bench_startup,
|
||||
bench_stats,
|
||||
bench_end_to_end,
|
||||
);
|
||||
|
||||
criterion_main!(cold_benches, hot_benches, misc_benches);
|
||||
@@ -13,6 +13,7 @@ FetchContent_Declare(
|
||||
FetchContent_MakeAvailable(Corrosion)
|
||||
|
||||
project("regorus-test")
|
||||
enable_testing()
|
||||
|
||||
corrosion_import_crate(
|
||||
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
|
||||
@@ -35,3 +36,10 @@ add_executable(regorus_test main.c)
|
||||
# Add path to <regorus-source-folder>/bindings/ffi
|
||||
target_include_directories(regorus_test PRIVATE "../ffi")
|
||||
target_link_libraries(regorus_test regorus_ffi)
|
||||
|
||||
add_executable(regorus_rvm_test rvm_tests.c)
|
||||
target_include_directories(regorus_rvm_test PRIVATE "../ffi")
|
||||
target_link_libraries(regorus_rvm_test regorus_ffi)
|
||||
|
||||
add_test(NAME regorus_c_engine COMMAND regorus_test)
|
||||
add_test(NAME regorus_c_rvm COMMAND regorus_rvm_test)
|
||||
|
||||
@@ -11,6 +11,20 @@ int main() {
|
||||
if (r.status != Ok)
|
||||
goto error;
|
||||
|
||||
// Configure the global pattern caches.
|
||||
RegorusCacheConfig cache_config = { .regex = 256, .glob = 128 };
|
||||
r = regorus_set_cache_config(cache_config);
|
||||
if (r.status != Ok)
|
||||
goto error;
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Raise the default col limit to 2000
|
||||
RegorusPolicyLengthConfig len_config = { .max_col = 2000, .max_file_bytes = 1048576, .max_lines = 20000 };
|
||||
r = regorus_engine_set_policy_length_config(engine, len_config);
|
||||
if (r.status != Ok)
|
||||
goto error;
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Load policies.
|
||||
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
|
||||
if (r.status != Ok)
|
||||
|
||||
289
bindings/c/rvm_tests.c
Normal file
289
bindings/c/rvm_tests.c
Normal file
@@ -0,0 +1,289 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "regorus.h"
|
||||
|
||||
static int assert_ok(RegorusResult r, const char* message) {
|
||||
if (r.status != Ok) {
|
||||
fprintf(stderr, "%s: %s\n", message, r.error_message ? r.error_message : "(no error)");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main() {
|
||||
RegorusResult result = {0};
|
||||
bool result_valid = false;
|
||||
RegorusProgram* program = NULL;
|
||||
RegorusBuffer* buffer = NULL;
|
||||
RegorusProgram* program2 = NULL;
|
||||
RegorusRvm* vm = NULL;
|
||||
RegorusProgram* host_program = NULL;
|
||||
RegorusRvm* host_vm = NULL;
|
||||
bool is_partial = false;
|
||||
int exit_code = 1;
|
||||
|
||||
const char* data_json =
|
||||
"{"
|
||||
" \"roles\": {"
|
||||
" \"alice\": [\"admin\", \"reader\"]"
|
||||
" }"
|
||||
"}";
|
||||
const char* input_json =
|
||||
"{"
|
||||
" \"user\": \"alice\","
|
||||
" \"actions\": [\"read\"]"
|
||||
"}";
|
||||
const char* module_text =
|
||||
"package demo\n"
|
||||
"default allow = false\n"
|
||||
"allow if {\n"
|
||||
" input.user == \"alice\"\n"
|
||||
" some role in data.roles[input.user]\n"
|
||||
" role == \"admin\"\n"
|
||||
" count(input.actions) > 0\n"
|
||||
"}\n";
|
||||
|
||||
const char* host_data_json = "{}";
|
||||
const char* host_input_json = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
|
||||
const char* host_module_text =
|
||||
"package demo\n"
|
||||
"import rego.v1\n"
|
||||
"default allow := false\n"
|
||||
"allow if {\n"
|
||||
" input.account.active == true\n"
|
||||
" details := __builtin_host_await(input.account.id, \"account\")\n"
|
||||
" details.tier == \"gold\"\n"
|
||||
"}\n";
|
||||
|
||||
RegorusPolicyModule module;
|
||||
module.id = "demo.rego";
|
||||
module.content = module_text;
|
||||
|
||||
const char* entry_points[] = {"data.demo.allow"};
|
||||
printf("Rego policy:\n%s\n", module_text);
|
||||
printf("Compiling program from modules...\n");
|
||||
result = regorus_program_compile_from_modules(
|
||||
data_json,
|
||||
&module,
|
||||
1,
|
||||
entry_points,
|
||||
1
|
||||
);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "compile program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
program = (RegorusProgram*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Generating assembly listing...\n");
|
||||
result = regorus_program_generate_listing(program);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "generate listing")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("Assembly listing:\n%s\n", result.output ? result.output : "(null)");
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Serializing program...\n");
|
||||
result = regorus_program_serialize_binary(program);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "serialize program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
buffer = (RegorusBuffer*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Deserializing program (%zu bytes)...\n", buffer->len);
|
||||
result = regorus_program_deserialize_binary(
|
||||
buffer->data,
|
||||
buffer->len,
|
||||
&is_partial
|
||||
);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "deserialize program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (is_partial) {
|
||||
fprintf(stderr, "deserialized program marked partial\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
program2 = (RegorusProgram*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Creating VM...\n");
|
||||
vm = regorus_rvm_new();
|
||||
if (!vm) {
|
||||
fprintf(stderr, "failed to allocate VM\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
printf("Loading program into VM...\n");
|
||||
result = regorus_rvm_load_program(vm, program2);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "load program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Setting data...\n");
|
||||
result = regorus_rvm_set_data(vm, data_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set data")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Setting input...\n");
|
||||
result = regorus_rvm_set_input(vm, input_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set input")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
printf("Executing entry point...\n");
|
||||
result = regorus_rvm_execute(vm);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "execute")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
printf("Execution result (data.demo.allow): %s\n",
|
||||
result.output ? result.output : "(null)");
|
||||
printf("Decision: user=alice action=read -> allow=%s\n",
|
||||
result.output ? result.output : "(null)");
|
||||
if (!result.output || strcmp(result.output, "true") != 0) {
|
||||
fprintf(stderr, "unexpected result: %s\n", result.output);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
printf("\n--- HostAwait example (suspendable execution) ---\n");
|
||||
RegorusPolicyModule host_module;
|
||||
host_module.id = "host_await.rego";
|
||||
host_module.content = host_module_text;
|
||||
|
||||
const char* host_entry_points[] = {"data.demo.allow"};
|
||||
result = regorus_program_compile_from_modules(
|
||||
host_data_json,
|
||||
&host_module,
|
||||
1,
|
||||
host_entry_points,
|
||||
1
|
||||
);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "compile host await program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
host_program = (RegorusProgram*)result.pointer_value;
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
host_vm = regorus_rvm_new();
|
||||
if (!host_vm) {
|
||||
fprintf(stderr, "failed to allocate host await VM\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
result = regorus_rvm_set_execution_mode(host_vm, 1);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set execution mode")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_load_program(host_vm, host_program);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "load host await program")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_set_data(host_vm, host_data_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set host data")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_set_input(host_vm, host_input_json);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "set host input")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_execute(host_vm);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "execute host await")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("HostAwait initial result: %s\n", result.output ? result.output : "(null)");
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_get_execution_state(host_vm);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "get execution state")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("Execution state: %s\n", result.output ? result.output : "(null)");
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
result = regorus_rvm_resume(host_vm, "{\"tier\":\"gold\"}", true);
|
||||
result_valid = true;
|
||||
if (!assert_ok(result, "resume host await")) {
|
||||
goto Cleanup;
|
||||
}
|
||||
printf("HostAwait resumed result: %s\n", result.output ? result.output : "(null)");
|
||||
|
||||
if (!result.output || strcmp(result.output, "true") != 0) {
|
||||
fprintf(stderr, "unexpected host await result\n");
|
||||
goto Cleanup;
|
||||
}
|
||||
regorus_result_drop(result);
|
||||
result_valid = false;
|
||||
|
||||
exit_code = 0;
|
||||
|
||||
Cleanup:
|
||||
if (result_valid) {
|
||||
regorus_result_drop(result);
|
||||
}
|
||||
if (host_vm) {
|
||||
regorus_rvm_drop(host_vm);
|
||||
}
|
||||
if (host_program) {
|
||||
regorus_program_drop(host_program);
|
||||
}
|
||||
if (vm) {
|
||||
regorus_rvm_drop(vm);
|
||||
}
|
||||
if (program2) {
|
||||
regorus_program_drop(program2);
|
||||
}
|
||||
if (buffer) {
|
||||
regorus_buffer_drop(buffer);
|
||||
}
|
||||
if (program) {
|
||||
regorus_program_drop(program);
|
||||
}
|
||||
return exit_code;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ FetchContent_MakeAvailable(Corrosion)
|
||||
|
||||
project("regorus-test")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
enable_testing()
|
||||
|
||||
# installable ffi target
|
||||
|
||||
@@ -83,3 +84,9 @@ install(FILES
|
||||
|
||||
add_executable(regorus_test main.cpp)
|
||||
target_link_libraries(regorus_test regorus_ffi::regorus_ffi)
|
||||
|
||||
add_executable(regorus_rvm_test rvm_tests.cpp)
|
||||
target_link_libraries(regorus_rvm_test regorus_ffi::regorus_ffi)
|
||||
|
||||
add_test(NAME regorus_cpp_engine COMMAND regorus_test)
|
||||
add_test(NAME regorus_cpp_rvm COMMAND regorus_rvm_test)
|
||||
|
||||
@@ -6,8 +6,19 @@ void example()
|
||||
// Create engine
|
||||
regorus::Engine engine;
|
||||
|
||||
// Configure the global pattern caches.
|
||||
RegorusCacheConfig cache_config = { 256, 128 };
|
||||
regorus::set_cache_config(cache_config);
|
||||
|
||||
engine.set_rego_v0(true);
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
RegorusPolicyLengthConfig len_config;
|
||||
// Raise the default col limit to 2000
|
||||
len_config.max_col = 2000;
|
||||
len_config.max_file_bytes = 1048576;
|
||||
len_config.max_lines = 20000;
|
||||
engine.set_policy_length_config(len_config);
|
||||
|
||||
// Add policies.
|
||||
engine.add_policy("objects.rego",R"(package objects
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef REGORUS_WRAPPER_HPP
|
||||
#define REGORUS_WRAPPER_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
@@ -8,8 +10,11 @@
|
||||
|
||||
namespace regorus {
|
||||
|
||||
class Result {
|
||||
public:
|
||||
class Buffer;
|
||||
class Program;
|
||||
|
||||
class Result {
|
||||
public:
|
||||
|
||||
operator bool() const { return result.status == RegorusStatus::Ok; }
|
||||
bool operator !() const { return result.status != RegorusStatus::Ok; }
|
||||
@@ -30,18 +35,39 @@ namespace regorus {
|
||||
}
|
||||
}
|
||||
|
||||
void* pointer() const {
|
||||
return result.pointer_value;
|
||||
}
|
||||
|
||||
Program program() const;
|
||||
Buffer buffer() const;
|
||||
|
||||
Result(RegorusResult r) : result(r) {}
|
||||
Result(Result&& other) noexcept : result(other.result) {
|
||||
other.result.output = nullptr;
|
||||
other.result.error_message = nullptr;
|
||||
other.result.pointer_value = nullptr;
|
||||
}
|
||||
Result& operator=(Result&& other) noexcept {
|
||||
if (this != &other) {
|
||||
regorus_result_drop(result);
|
||||
result = other.result;
|
||||
other.result.output = nullptr;
|
||||
other.result.error_message = nullptr;
|
||||
other.result.pointer_value = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Result() {
|
||||
regorus_result_drop(result);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Engine;
|
||||
RegorusResult result;
|
||||
|
||||
Result(RegorusResult r) : result(r) {}
|
||||
private:
|
||||
Result(const Result&) = delete;
|
||||
Result(Result&&) = delete;
|
||||
Result& operator=(const Result&) = delete;
|
||||
|
||||
};
|
||||
@@ -105,10 +131,22 @@ namespace regorus {
|
||||
Result get_coverage_report_pretty() {
|
||||
return Result(regorus_engine_get_coverage_report_pretty(engine));
|
||||
}
|
||||
|
||||
|
||||
Result set_policy_length_config(RegorusPolicyLengthConfig config) {
|
||||
return Result(regorus_engine_set_policy_length_config(engine, config));
|
||||
}
|
||||
|
||||
Result clear_policy_length_config() {
|
||||
return Result(regorus_engine_clear_policy_length_config(engine));
|
||||
}
|
||||
|
||||
~Engine() {
|
||||
regorus_engine_drop(engine);
|
||||
}
|
||||
|
||||
RegorusEngine* raw() const {
|
||||
return engine;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
@@ -119,6 +157,255 @@ namespace regorus {
|
||||
Engine(Engine&&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
};
|
||||
|
||||
inline Result set_cache_config(RegorusCacheConfig config) {
|
||||
return Result(regorus_set_cache_config(config));
|
||||
}
|
||||
|
||||
inline Result clear_cache() {
|
||||
return Result(regorus_clear_cache());
|
||||
}
|
||||
|
||||
class CompiledPolicy {
|
||||
public:
|
||||
explicit CompiledPolicy(RegorusCompiledPolicy* p) : policy(p) {}
|
||||
|
||||
Result eval_with_input(const char* input_json) {
|
||||
return Result(regorus_compiled_policy_eval_with_input(policy, input_json));
|
||||
}
|
||||
|
||||
Result get_policy_info() {
|
||||
return Result(regorus_compiled_policy_get_policy_info(policy));
|
||||
}
|
||||
|
||||
RegorusCompiledPolicy* raw() const {
|
||||
return policy;
|
||||
}
|
||||
|
||||
~CompiledPolicy() {
|
||||
if (policy) {
|
||||
regorus_compiled_policy_drop(policy);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusCompiledPolicy* policy;
|
||||
CompiledPolicy(const CompiledPolicy&) = delete;
|
||||
CompiledPolicy(CompiledPolicy&&) = delete;
|
||||
CompiledPolicy& operator=(const CompiledPolicy&) = delete;
|
||||
};
|
||||
|
||||
class Buffer {
|
||||
public:
|
||||
Buffer() : buffer(nullptr) {}
|
||||
explicit Buffer(RegorusBuffer* b) : buffer(b) {}
|
||||
|
||||
const std::uint8_t* data() const {
|
||||
return buffer ? buffer->data : nullptr;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return buffer ? buffer->len : 0;
|
||||
}
|
||||
|
||||
RegorusBuffer* raw() const {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
~Buffer() {
|
||||
if (buffer) {
|
||||
regorus_buffer_drop(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusBuffer* buffer;
|
||||
Buffer(const Buffer&) = delete;
|
||||
Buffer(Buffer&&) = delete;
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
};
|
||||
|
||||
class Program {
|
||||
public:
|
||||
Program() : program(regorus_program_new()) {}
|
||||
explicit Program(RegorusProgram* p) : program(p) {}
|
||||
|
||||
static Result compile_from_policy(
|
||||
RegorusCompiledPolicy* compiled_policy,
|
||||
const char* const* entry_points,
|
||||
size_t entry_points_len
|
||||
) {
|
||||
return Result(regorus_program_compile_from_policy(
|
||||
compiled_policy,
|
||||
entry_points,
|
||||
entry_points_len
|
||||
));
|
||||
}
|
||||
|
||||
static Result compile_from_modules(
|
||||
const char* data_json,
|
||||
const RegorusPolicyModule* modules,
|
||||
size_t modules_len,
|
||||
const char* const* entry_points,
|
||||
size_t entry_points_len
|
||||
) {
|
||||
return Result(regorus_program_compile_from_modules(
|
||||
data_json,
|
||||
modules,
|
||||
modules_len,
|
||||
entry_points,
|
||||
entry_points_len
|
||||
));
|
||||
}
|
||||
|
||||
static Result compile_from_engine(
|
||||
RegorusEngine* engine,
|
||||
const char* const* entry_points,
|
||||
size_t entry_points_len
|
||||
) {
|
||||
return Result(regorus_engine_compile_program_with_entrypoints(
|
||||
engine,
|
||||
entry_points,
|
||||
entry_points_len
|
||||
));
|
||||
}
|
||||
|
||||
Result serialize_binary() const {
|
||||
return Result(regorus_program_serialize_binary(program));
|
||||
}
|
||||
|
||||
static Result deserialize_binary(
|
||||
const std::uint8_t* data,
|
||||
size_t len,
|
||||
bool* is_partial
|
||||
) {
|
||||
return Result(regorus_program_deserialize_binary(data, len, is_partial));
|
||||
}
|
||||
|
||||
Result generate_listing() const {
|
||||
return Result(regorus_program_generate_listing(program));
|
||||
}
|
||||
|
||||
Result generate_tabular_listing() const {
|
||||
return Result(regorus_program_generate_tabular_listing(program));
|
||||
}
|
||||
|
||||
RegorusProgram* raw() const {
|
||||
return program;
|
||||
}
|
||||
|
||||
~Program() {
|
||||
if (program) {
|
||||
regorus_program_drop(program);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusProgram* program;
|
||||
Program(const Program&) = delete;
|
||||
Program(Program&&) = delete;
|
||||
Program& operator=(const Program&) = delete;
|
||||
};
|
||||
|
||||
inline Program Result::program() const {
|
||||
return Program(reinterpret_cast<RegorusProgram*>(result.pointer_value));
|
||||
}
|
||||
|
||||
inline Buffer Result::buffer() const {
|
||||
return Buffer(reinterpret_cast<RegorusBuffer*>(result.pointer_value));
|
||||
}
|
||||
|
||||
class Rvm {
|
||||
public:
|
||||
Rvm() : vm(regorus_rvm_new()) {}
|
||||
explicit Rvm(RegorusRvm* v) : vm(v) {}
|
||||
|
||||
static Result create_with_policy(RegorusCompiledPolicy* compiled_policy) {
|
||||
return Result(regorus_rvm_new_with_policy(compiled_policy));
|
||||
}
|
||||
|
||||
Result load_program(const Program& program) {
|
||||
return Result(regorus_rvm_load_program(vm, program.raw()));
|
||||
}
|
||||
|
||||
Result set_data(const char* data_json) {
|
||||
return Result(regorus_rvm_set_data(vm, data_json));
|
||||
}
|
||||
|
||||
Result set_input(const char* input_json) {
|
||||
return Result(regorus_rvm_set_input(vm, input_json));
|
||||
}
|
||||
|
||||
Result set_max_instructions(size_t max_instructions) {
|
||||
return Result(regorus_rvm_set_max_instructions(vm, max_instructions));
|
||||
}
|
||||
|
||||
Result set_strict_builtin_errors(bool strict) {
|
||||
return Result(regorus_rvm_set_strict_builtin_errors(vm, strict));
|
||||
}
|
||||
|
||||
Result set_execution_mode(std::uint8_t mode) {
|
||||
return Result(regorus_rvm_set_execution_mode(vm, mode));
|
||||
}
|
||||
|
||||
Result set_step_mode(bool enabled) {
|
||||
return Result(regorus_rvm_set_step_mode(vm, enabled));
|
||||
}
|
||||
|
||||
Result set_execution_timer_config(bool has_config, RegorusExecutionTimerConfig config) {
|
||||
return Result(regorus_rvm_set_execution_timer_config(vm, has_config, config));
|
||||
}
|
||||
|
||||
Result execute() {
|
||||
return Result(regorus_rvm_execute(vm));
|
||||
}
|
||||
|
||||
Result execute_entry_point_by_name(const char* entry_point) {
|
||||
return Result(regorus_rvm_execute_entry_point_by_name(vm, entry_point));
|
||||
}
|
||||
|
||||
Result execute_entry_point_by_index(size_t index) {
|
||||
return Result(regorus_rvm_execute_entry_point_by_index(vm, index));
|
||||
}
|
||||
|
||||
Result resume(const char* resume_value_json, bool has_value) {
|
||||
return Result(regorus_rvm_resume(vm, resume_value_json, has_value));
|
||||
}
|
||||
|
||||
Result get_execution_state() {
|
||||
return Result(regorus_rvm_get_execution_state(vm));
|
||||
}
|
||||
|
||||
RegorusRvm* raw() const {
|
||||
return vm;
|
||||
}
|
||||
|
||||
~Rvm() {
|
||||
if (vm) {
|
||||
regorus_rvm_drop(vm);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RegorusRvm* vm;
|
||||
Rvm(const Rvm&) = delete;
|
||||
Rvm(Rvm&&) = delete;
|
||||
Rvm& operator=(const Rvm&) = delete;
|
||||
};
|
||||
|
||||
inline Result compile_policy_with_entrypoint(
|
||||
const char* data_json,
|
||||
const RegorusPolicyModule* modules,
|
||||
size_t modules_len,
|
||||
const char* entry_point
|
||||
) {
|
||||
return Result(regorus_compile_policy_with_entrypoint(
|
||||
data_json,
|
||||
modules,
|
||||
modules_len,
|
||||
entry_point
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // REGORUS_WRAPPER_HPP
|
||||
|
||||
261
bindings/cpp/rvm_tests.cpp
Normal file
261
bindings/cpp/rvm_tests.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "regorus.hpp"
|
||||
|
||||
int main() {
|
||||
const char* data_json =
|
||||
"{"
|
||||
" \"roles\": {"
|
||||
" \"alice\": [\"admin\", \"reader\"]"
|
||||
" }"
|
||||
"}";
|
||||
const char* input_json =
|
||||
"{"
|
||||
" \"user\": \"alice\","
|
||||
" \"actions\": [\"read\"]"
|
||||
"}";
|
||||
const char* module_text =
|
||||
"package demo\n"
|
||||
"default allow = false\n"
|
||||
"allow if {\n"
|
||||
" input.user == \"alice\"\n"
|
||||
" some role in data.roles[input.user]\n"
|
||||
" role == \"admin\"\n"
|
||||
" count(input.actions) > 0\n"
|
||||
"}\n";
|
||||
|
||||
const char* host_data_json = "{}";
|
||||
const char* host_input_json = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
|
||||
const char* host_module_text =
|
||||
"package demo\n"
|
||||
"import rego.v1\n"
|
||||
"default allow := false\n"
|
||||
"allow if {\n"
|
||||
" input.account.active == true\n"
|
||||
" details := __builtin_host_await(input.account.id, \"account\")\n"
|
||||
" details.tier == \"gold\"\n"
|
||||
"}\n";
|
||||
|
||||
RegorusPolicyModule module;
|
||||
module.id = "demo.rego";
|
||||
module.content = module_text;
|
||||
|
||||
const char* entry_points[] = {"data.demo.allow"};
|
||||
std::cout << "Rego policy:\n" << module_text << std::endl;
|
||||
std::cout << "Compiling program from modules..." << std::endl;
|
||||
auto program_result = regorus::Program::compile_from_modules(
|
||||
data_json,
|
||||
&module,
|
||||
1,
|
||||
entry_points,
|
||||
1
|
||||
);
|
||||
if (!program_result) {
|
||||
std::cerr << "compile program (modules): " << program_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program program = program_result.program();
|
||||
|
||||
std::cout << "Generating assembly listing..." << std::endl;
|
||||
auto listing_result = program.generate_listing();
|
||||
if (!listing_result) {
|
||||
std::cerr << "generate listing: " << listing_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Assembly listing:\n" << listing_result.output() << std::endl;
|
||||
|
||||
std::cout << "Serializing program..." << std::endl;
|
||||
auto serialize_result = program.serialize_binary();
|
||||
if (!serialize_result) {
|
||||
std::cerr << "serialize program: " << serialize_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Buffer buffer(reinterpret_cast<RegorusBuffer*>(serialize_result.pointer()));
|
||||
bool is_partial = false;
|
||||
std::cout << "Deserializing program (" << buffer.size() << " bytes)..." << std::endl;
|
||||
auto deserialize_result = regorus::Program::deserialize_binary(
|
||||
buffer.data(),
|
||||
buffer.size(),
|
||||
&is_partial
|
||||
);
|
||||
if (!deserialize_result) {
|
||||
std::cerr << "deserialize program: " << deserialize_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (is_partial) {
|
||||
std::cerr << "deserialized program marked partial" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program program2 = deserialize_result.program();
|
||||
|
||||
{
|
||||
std::cout << "Creating VM..." << std::endl;
|
||||
regorus::Rvm vm;
|
||||
auto load_result = vm.load_program(program2);
|
||||
if (!load_result) {
|
||||
std::cerr << "load program: " << load_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting data..." << std::endl;
|
||||
auto data_result = vm.set_data(data_json);
|
||||
if (!data_result) {
|
||||
std::cerr << "set data: " << data_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting input..." << std::endl;
|
||||
auto input_result = vm.set_input(input_json);
|
||||
if (!input_result) {
|
||||
std::cerr << "set input: " << input_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Executing entry point..." << std::endl;
|
||||
auto exec_result = vm.execute();
|
||||
if (!exec_result) {
|
||||
std::cerr << "execute: " << exec_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Execution result (data.demo.allow): " << exec_result.output() << std::endl;
|
||||
std::cout << "Decision: user=alice action=read -> allow=" << exec_result.output() << std::endl;
|
||||
if (std::string(exec_result.output()) != "true") {
|
||||
std::cerr << "unexpected result: " << exec_result.output() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
regorus::Engine engine;
|
||||
std::cout << "Compiling program from engine..." << std::endl;
|
||||
auto add_policy_result = engine.add_policy("demo.rego", module_text);
|
||||
if (!add_policy_result) {
|
||||
std::cerr << "engine add policy: " << add_policy_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto engine_program_result = regorus::Program::compile_from_engine(
|
||||
engine.raw(),
|
||||
entry_points,
|
||||
1
|
||||
);
|
||||
if (!engine_program_result) {
|
||||
std::cerr << "compile program (engine): " << engine_program_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program engine_program = engine_program_result.program();
|
||||
|
||||
regorus::Rvm engine_vm;
|
||||
auto engine_load_result = engine_vm.load_program(engine_program);
|
||||
if (!engine_load_result) {
|
||||
std::cerr << "engine load program: " << engine_load_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting engine data..." << std::endl;
|
||||
auto engine_data_result = engine_vm.set_data(data_json);
|
||||
if (!engine_data_result) {
|
||||
std::cerr << "engine set data: " << engine_data_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Setting engine input..." << std::endl;
|
||||
auto engine_input_result = engine_vm.set_input(input_json);
|
||||
if (!engine_input_result) {
|
||||
std::cerr << "engine set input: " << engine_input_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Executing engine entry point..." << std::endl;
|
||||
auto engine_exec_result = engine_vm.execute();
|
||||
if (!engine_exec_result) {
|
||||
std::cerr << "engine execute: " << engine_exec_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Engine execution result (data.demo.allow): " << engine_exec_result.output() << std::endl;
|
||||
std::cout << "Decision: user=alice action=read -> allow=" << engine_exec_result.output() << std::endl;
|
||||
if (std::string(engine_exec_result.output()) != "true") {
|
||||
std::cerr << "unexpected engine result: " << engine_exec_result.output() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "\n--- HostAwait example (suspendable execution) ---" << std::endl;
|
||||
RegorusPolicyModule host_module;
|
||||
host_module.id = "host_await.rego";
|
||||
host_module.content = host_module_text;
|
||||
const char* host_entry_points[] = {"data.demo.allow"};
|
||||
|
||||
auto host_program_result = regorus::Program::compile_from_modules(
|
||||
host_data_json,
|
||||
&host_module,
|
||||
1,
|
||||
host_entry_points,
|
||||
1
|
||||
);
|
||||
if (!host_program_result) {
|
||||
std::cerr << "compile host await program: " << host_program_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
regorus::Program host_program = host_program_result.program();
|
||||
regorus::Rvm host_vm;
|
||||
auto host_mode_result = host_vm.set_execution_mode(1);
|
||||
if (!host_mode_result) {
|
||||
std::cerr << "set execution mode: " << host_mode_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_load_result = host_vm.load_program(host_program);
|
||||
if (!host_load_result) {
|
||||
std::cerr << "load host await program: " << host_load_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_data_result = host_vm.set_data(host_data_json);
|
||||
if (!host_data_result) {
|
||||
std::cerr << "set host data: " << host_data_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_input_result = host_vm.set_input(host_input_json);
|
||||
if (!host_input_result) {
|
||||
std::cerr << "set host input: " << host_input_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto host_exec_result = host_vm.execute();
|
||||
if (!host_exec_result) {
|
||||
std::cerr << "execute host await: " << host_exec_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "HostAwait initial result: " << host_exec_result.output() << std::endl;
|
||||
|
||||
auto host_state_result = host_vm.get_execution_state();
|
||||
if (!host_state_result) {
|
||||
std::cerr << "get execution state: " << host_state_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Execution state: " << host_state_result.output() << std::endl;
|
||||
|
||||
auto host_resume_result = host_vm.resume("{\"tier\":\"gold\"}", true);
|
||||
if (!host_resume_result) {
|
||||
std::cerr << "resume host await: " << host_resume_result.error() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "HostAwait resumed result: " << host_resume_result.output() << std::endl;
|
||||
if (std::string(host_resume_result.output()) != "true") {
|
||||
std::cerr << "unexpected host await result: " << host_resume_result.output() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
|
||||
<PackageReference Include="Regorus" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Benchmarks
|
||||
public class CompiledPolicyEvaluationBenchmark
|
||||
{
|
||||
private static readonly string TestDataPath = Path.Combine(
|
||||
Directory.GetCurrentDirectory(),
|
||||
Directory.GetCurrentDirectory(),
|
||||
"..", "..", "..",
|
||||
"benches", "evaluation", "test_data"
|
||||
);
|
||||
@@ -33,7 +33,7 @@ namespace Benchmarks
|
||||
private static readonly string[] PolicyNames = new[]
|
||||
{
|
||||
"rbac_policy",
|
||||
"api_access_policy",
|
||||
"api_access_policy",
|
||||
"data_sensitivity_policy",
|
||||
"time_based_policy",
|
||||
"data_processing_policy",
|
||||
@@ -46,21 +46,21 @@ namespace Benchmarks
|
||||
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
|
||||
{
|
||||
var result = new List<(string Policy, string[] Inputs)>();
|
||||
|
||||
|
||||
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
|
||||
{
|
||||
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
|
||||
var policy = File.ReadAllText(policyPath);
|
||||
|
||||
|
||||
var inputs = inputFiles.Select(inputFile =>
|
||||
{
|
||||
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
|
||||
return File.ReadAllText(inputPath);
|
||||
}).ToArray();
|
||||
|
||||
|
||||
result.Add((policy, inputs));
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -68,14 +68,14 @@ namespace Benchmarks
|
||||
{
|
||||
var policiesWithInputs = LoadPoliciesWithInputs();
|
||||
var compiledPolicies = new List<CompiledPolicy>();
|
||||
|
||||
|
||||
foreach (var (policy, _) in policiesWithInputs)
|
||||
{
|
||||
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
|
||||
var modules = new[] { new PolicyModule("policy.rego", policy) };
|
||||
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
|
||||
compiledPolicies.Add(compiled);
|
||||
}
|
||||
|
||||
|
||||
return compiledPolicies;
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ namespace Benchmarks
|
||||
var cpuCount = Environment.ProcessorCount;
|
||||
var maxThreads = cpuCount * 2;
|
||||
var threadCounts = new List<int> { 1, 2 };
|
||||
|
||||
|
||||
// Add even numbers from 4 to maxThreads
|
||||
for (int i = 4; i <= maxThreads; i += 2)
|
||||
{
|
||||
threadCounts.Add(i);
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine($"Running compiled policy benchmark with max_threads: {maxThreads}");
|
||||
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
|
||||
Console.WriteLine();
|
||||
@@ -120,19 +120,19 @@ namespace Benchmarks
|
||||
const int durationSeconds = 3;
|
||||
var policiesWithInputs = LoadPoliciesWithInputs();
|
||||
List<CompiledPolicy>? compiledPolicies = null;
|
||||
|
||||
|
||||
if (useSharedPolicies)
|
||||
{
|
||||
compiledPolicies = PrepareSharedCompiledPolicies();
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
|
||||
|
||||
|
||||
// Warmup phase
|
||||
var (_, _, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: true);
|
||||
|
||||
|
||||
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
|
||||
|
||||
|
||||
// Actual benchmark phase
|
||||
var (totalEvaluations, evaluationTime, policyCounters, allocatedBytes) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: false);
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace Benchmarks
|
||||
{
|
||||
foreach (var policy in compiledPolicies)
|
||||
{
|
||||
policy.Dispose();
|
||||
DisposeCompiledPolicy(policy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@ namespace Benchmarks
|
||||
}
|
||||
|
||||
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters, long allocatedBytes) RunBenchmarkPhase(
|
||||
int threads,
|
||||
int durationSeconds,
|
||||
int threads,
|
||||
int durationSeconds,
|
||||
List<(string Policy, string[] Inputs)> policiesWithInputs,
|
||||
List<CompiledPolicy>? compiledPolicies,
|
||||
bool useSharedPolicies,
|
||||
@@ -208,10 +208,10 @@ namespace Benchmarks
|
||||
}
|
||||
|
||||
barrier.SignalAndWait();
|
||||
|
||||
|
||||
int evaluationCount = 0;
|
||||
var localEvaluationTime = TimeSpan.Zero;
|
||||
|
||||
|
||||
while (!stopExecution)
|
||||
{
|
||||
// Use different policy for each iteration
|
||||
@@ -226,23 +226,29 @@ namespace Benchmarks
|
||||
{
|
||||
// Measure only the evaluation call
|
||||
var evalStopwatch = Stopwatch.StartNew();
|
||||
|
||||
|
||||
if (useSharedPolicies)
|
||||
{
|
||||
var result = compiledPolicies![policyIdx].EvalWithInput(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compile policy in each iteration
|
||||
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
|
||||
// Compile policy in each iteration.
|
||||
var modules = new[] { new PolicyModule("policy.rego", policy) };
|
||||
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
|
||||
var result = compiled.EvalWithInput(input);
|
||||
compiled.Dispose();
|
||||
try
|
||||
{
|
||||
var result = compiled.EvalWithInput(input);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCompiledPolicy(compiled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
evalStopwatch.Stop();
|
||||
localEvaluationTime += evalStopwatch.Elapsed;
|
||||
|
||||
|
||||
// Track successful evaluations (only during actual benchmark, not warmup)
|
||||
if (!isWarmup)
|
||||
{
|
||||
@@ -256,10 +262,10 @@ namespace Benchmarks
|
||||
{
|
||||
// Ignore evaluation errors for benchmarking purposes
|
||||
}
|
||||
|
||||
|
||||
evaluationCount++;
|
||||
}
|
||||
|
||||
|
||||
// Store the actual evaluation time for this thread
|
||||
if (!isWarmup)
|
||||
{
|
||||
@@ -284,11 +290,23 @@ namespace Benchmarks
|
||||
|
||||
var totalEvaluations = policyCounters.Values.Sum();
|
||||
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
|
||||
|
||||
|
||||
// Use pure evaluation time (consistent with Rust benchmark)
|
||||
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
|
||||
|
||||
|
||||
return (totalEvaluations, evaluationTime, policyCounters, allocatedBytes);
|
||||
}
|
||||
|
||||
private static void DisposeCompiledPolicy(CompiledPolicy policy)
|
||||
{
|
||||
try
|
||||
{
|
||||
policy.Dispose();
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
Console.WriteLine($"Warning: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Benchmarks
|
||||
public class EngineEvaluationBenchmark
|
||||
{
|
||||
private static readonly string TestDataPath = Path.Combine(
|
||||
Directory.GetCurrentDirectory(),
|
||||
Directory.GetCurrentDirectory(),
|
||||
"..", "..", "..",
|
||||
"benches", "evaluation", "test_data"
|
||||
);
|
||||
@@ -33,7 +33,7 @@ namespace Benchmarks
|
||||
private static readonly string[] PolicyNames = new[]
|
||||
{
|
||||
"rbac_policy",
|
||||
"api_access_policy",
|
||||
"api_access_policy",
|
||||
"data_sensitivity_policy",
|
||||
"time_based_policy",
|
||||
"data_processing_policy",
|
||||
@@ -46,21 +46,21 @@ namespace Benchmarks
|
||||
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
|
||||
{
|
||||
var result = new List<(string Policy, string[] Inputs)>();
|
||||
|
||||
|
||||
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
|
||||
{
|
||||
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
|
||||
var policy = File.ReadAllText(policyPath);
|
||||
|
||||
|
||||
var inputs = inputFiles.Select(inputFile =>
|
||||
{
|
||||
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
|
||||
return File.ReadAllText(inputPath);
|
||||
}).ToArray();
|
||||
|
||||
|
||||
result.Add((policy, inputs));
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -68,12 +68,12 @@ namespace Benchmarks
|
||||
{
|
||||
var policiesWithInputs = LoadPoliciesWithInputs();
|
||||
var engines = new List<Engine>();
|
||||
|
||||
|
||||
foreach (var (policy, _) in policiesWithInputs)
|
||||
{
|
||||
var engine = new Engine();
|
||||
engine.AddPolicy("policy.rego", policy);
|
||||
|
||||
|
||||
// Warm up the engine to ensure it's fully prepared for evaluation
|
||||
// This prevents each cloned engine from repeating preparation work
|
||||
engine.SetInputJson("{}");
|
||||
@@ -85,10 +85,10 @@ namespace Benchmarks
|
||||
{
|
||||
// Ignore warmup errors
|
||||
}
|
||||
|
||||
|
||||
engines.Add(engine);
|
||||
}
|
||||
|
||||
|
||||
return engines;
|
||||
}
|
||||
|
||||
@@ -97,13 +97,13 @@ namespace Benchmarks
|
||||
var cpuCount = Environment.ProcessorCount;
|
||||
var maxThreads = cpuCount * 2;
|
||||
var threadCounts = new List<int> { 1, 2 };
|
||||
|
||||
|
||||
// Add even numbers from 4 to maxThreads
|
||||
for (int i = 4; i <= maxThreads; i += 2)
|
||||
{
|
||||
threadCounts.Add(i);
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine($"Running engine benchmark with max_threads: {maxThreads}");
|
||||
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
|
||||
Console.WriteLine();
|
||||
@@ -132,14 +132,14 @@ namespace Benchmarks
|
||||
const int warmupSeconds = 3;
|
||||
const int durationSeconds = 3;
|
||||
var policiesWithInputs = LoadPoliciesWithInputs();
|
||||
|
||||
|
||||
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
|
||||
|
||||
|
||||
// Warmup phase
|
||||
var (_, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, useClonedEngines, isWarmup: true);
|
||||
|
||||
|
||||
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
|
||||
|
||||
|
||||
// Actual benchmark phase
|
||||
var (totalEvaluations, evaluationTime, policyCounters) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, useClonedEngines, isWarmup: false);
|
||||
|
||||
@@ -165,8 +165,8 @@ namespace Benchmarks
|
||||
}
|
||||
|
||||
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters) RunBenchmarkPhase(
|
||||
int threads,
|
||||
int durationSeconds,
|
||||
int threads,
|
||||
int durationSeconds,
|
||||
List<(string Policy, string[] Inputs)> policiesWithInputs,
|
||||
bool useClonedEngines,
|
||||
bool isWarmup)
|
||||
@@ -199,10 +199,10 @@ namespace Benchmarks
|
||||
tasks[threadId] = Task.Run(() =>
|
||||
{
|
||||
barrier.SignalAndWait();
|
||||
|
||||
|
||||
int evaluationCount = 0;
|
||||
var localEvaluationTime = TimeSpan.Zero;
|
||||
|
||||
|
||||
while (!stopExecution)
|
||||
{
|
||||
// Use different policy for each iteration
|
||||
@@ -217,7 +217,7 @@ namespace Benchmarks
|
||||
{
|
||||
// Measure only the engine operations
|
||||
var evalStopwatch = Stopwatch.StartNew();
|
||||
|
||||
|
||||
Engine engine;
|
||||
if (useClonedEngines)
|
||||
{
|
||||
@@ -228,14 +228,14 @@ namespace Benchmarks
|
||||
engine = new Engine();
|
||||
engine.AddPolicy("policy.rego", policy);
|
||||
}
|
||||
|
||||
|
||||
engine.SetInputJson(input);
|
||||
var result = engine.EvalRule("data.bench.allow");
|
||||
engine.Dispose();
|
||||
|
||||
|
||||
evalStopwatch.Stop();
|
||||
localEvaluationTime += evalStopwatch.Elapsed;
|
||||
|
||||
|
||||
// Track successful evaluations (only during actual benchmark, not warmup)
|
||||
if (!isWarmup)
|
||||
{
|
||||
@@ -249,10 +249,10 @@ namespace Benchmarks
|
||||
{
|
||||
// Ignore evaluation errors for benchmarking purposes
|
||||
}
|
||||
|
||||
|
||||
evaluationCount++;
|
||||
}
|
||||
|
||||
|
||||
// Store the actual evaluation time for this thread
|
||||
if (!isWarmup)
|
||||
{
|
||||
@@ -283,10 +283,10 @@ namespace Benchmarks
|
||||
|
||||
var totalEvaluations = policyCounters.Values.Sum();
|
||||
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
|
||||
|
||||
|
||||
// Use pure evaluation time (consistent with Rust benchmark)
|
||||
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
|
||||
|
||||
|
||||
return (totalEvaluations, evaluationTime, policyCounters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Benchmarks
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("=== Regorus C# Benchmarks ===\n");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Running Engine Evaluation Benchmark...");
|
||||
@@ -17,9 +17,9 @@ namespace Benchmarks
|
||||
{
|
||||
Console.WriteLine($"Engine benchmark failed: {ex.Message}");
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine("\n" + new string('=', 80) + "\n");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Running Compiled Policy Evaluation Benchmark...");
|
||||
@@ -29,7 +29,7 @@ namespace Benchmarks
|
||||
{
|
||||
Console.WriteLine($"Compiled policy benchmark failed: {ex.Message}");
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine("\n=== Benchmarks Complete ===");
|
||||
}
|
||||
}
|
||||
|
||||
15
bindings/csharp/Directory.Packages.props
Normal file
15
bindings/csharp/Directory.Packages.props
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<RegorusPackageVersion>0.9.1</RegorusPackageVersion>
|
||||
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Centralize Regorus package version with optional CI suffix -->
|
||||
<PackageVersion Include="Regorus" Version="$(RegorusPackageVersion)$(RegorusPackageVersionSuffix)" />
|
||||
<PackageVersion Include="MSTest" Version="3.8.2" />
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageVersion Include="YamlDotNet" Version="13.7.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -29,4 +29,124 @@ Once the workflow run completes, the generated Nuget can be downloaded by follow
|
||||
|
||||
## Local
|
||||
|
||||
TODO
|
||||
The `cargo xtask` runner provides helpers for local builds:
|
||||
|
||||
1. `cargo xtask ffi` builds the `bindings/ffi` crate for the host platform in debug mode. Add `--target <triple>` (repeatable) to cross-compile, or `--release` to produce optimised artefacts. Results land under `bindings/ffi/target/<triple>/<profile>`.
|
||||
2. `cargo xtask nuget` reuses those artefacts to pack the C# library. It defaults to debug builds for the host but accepts `--target`, `--release`, `--artifacts-dir <path>` to reuse existing binaries, and `--enforce-artifacts` to require every officially supported platform.
|
||||
3. `cargo xtask test-csharp` ensures a NuGet is available (rebuilding when required or when `--force-nuget` is passed) and then runs `Regorus.Tests`, `TestApp`, and `TargetExampleApp` against it. The command accepts the same build flags as `cargo xtask nuget`.
|
||||
|
||||
## Memory Usage Safeguards
|
||||
|
||||
The C# bindings expose allocator-backed memory tracking utilities via the static `Regorus.MemoryLimits` helper. Typical usage:
|
||||
|
||||
```csharp
|
||||
// Restrict total allocations to 128 MiB for the process
|
||||
Regorus.MemoryLimits.SetGlobalMemoryLimit(128 * 1024 * 1024);
|
||||
|
||||
// Optional: tune how frequently each thread flushes its allocation counters
|
||||
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(256 * 1024);
|
||||
|
||||
// Engine operations throw InvalidOperationException with the allocator message if the budget is exceeded
|
||||
using var engine = new Regorus.Engine();
|
||||
var veryLargeJson = new string('x', 128 * 1024);
|
||||
try
|
||||
{
|
||||
engine.SetInputJson(veryLargeJson);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Console.WriteLine($"Allocator reported: {ex.Message}");
|
||||
}
|
||||
|
||||
// Restore defaults once done
|
||||
Regorus.MemoryLimits.SetGlobalMemoryLimit(null);
|
||||
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(null);
|
||||
```
|
||||
|
||||
See bindings/csharp/Regorus.Tests/RegorusTests.cs for scenario coverage and bindings/csharp/TargetExampleApp/Program.cs for end-to-end usage.
|
||||
|
||||
## RVM Usage Example
|
||||
|
||||
The RVM API lets you compile a program from modules/entrypoints and execute it in a VM:
|
||||
|
||||
```csharp
|
||||
using Regorus;
|
||||
|
||||
const string Policy = """
|
||||
package demo
|
||||
default allow = false
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
some role in data.roles[input.user]
|
||||
role == "admin"
|
||||
}
|
||||
""";
|
||||
|
||||
const string Data = """
|
||||
{ "roles": { "alice": ["admin"] } }
|
||||
""";
|
||||
|
||||
const string Input = """
|
||||
{ "user": "alice" }
|
||||
""";
|
||||
|
||||
var modules = new[] { new PolicyModule("demo.rego", Policy) };
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Program.CompileFromModules(Data, modules, entryPoints);
|
||||
var listing = program.GenerateListing();
|
||||
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson(Data);
|
||||
vm.SetInputJson(Input);
|
||||
|
||||
var result = vm.Execute();
|
||||
Console.WriteLine($"allow: {result}");
|
||||
```
|
||||
|
||||
## Azure RBAC Condition Evaluation
|
||||
|
||||
Evaluate Azure RBAC condition expressions directly with a JSON evaluation context:
|
||||
|
||||
```csharp
|
||||
using Regorus;
|
||||
|
||||
const string Condition = "@Resource[owner] StringEquals 'alice'";
|
||||
const string ContextJson = """
|
||||
{
|
||||
"principal": {
|
||||
"id": "user-1",
|
||||
"principal_type": "User",
|
||||
"custom_security_attributes": {}
|
||||
},
|
||||
"resource": {
|
||||
"id": "/subscriptions/s1",
|
||||
"resource_type": "Microsoft.Storage/storageAccounts",
|
||||
"scope": "/subscriptions/s1",
|
||||
"attributes": {
|
||||
"owner": "alice",
|
||||
"confidential": true
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"action": "Microsoft.Storage/storageAccounts/read",
|
||||
"data_action": null,
|
||||
"attributes": {
|
||||
"clientIP": "10.0.0.1"
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"is_private_link": null,
|
||||
"private_endpoint": null,
|
||||
"subnet": null,
|
||||
"utc_now": "2023-05-01T12:00:00Z"
|
||||
},
|
||||
"action": "Microsoft.Storage/storageAccounts/read",
|
||||
"suboperation": null
|
||||
}
|
||||
""";
|
||||
|
||||
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
|
||||
Console.WriteLine($"RBAC condition allowed: {allowed}");
|
||||
```
|
||||
|
||||
191
bindings/csharp/Regorus.Tests/AliasRegistryTests.cs
Normal file
191
bindings/csharp/Regorus.Tests/AliasRegistryTests.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class AliasRegistryTests
|
||||
{
|
||||
private const string AliasesJson = @"[{
|
||||
""namespace"": ""Microsoft.Storage"",
|
||||
""resourceTypes"": [{
|
||||
""resourceType"": ""storageAccounts"",
|
||||
""aliases"": [{
|
||||
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
||||
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
||||
""paths"": []
|
||||
}, {
|
||||
""name"": ""Microsoft.Storage/storageAccounts/accessTier"",
|
||||
""defaultPath"": ""properties.accessTier"",
|
||||
""paths"": []
|
||||
}]
|
||||
}]
|
||||
}]";
|
||||
|
||||
private const string ManifestJson = @"{
|
||||
""dataNamespace"": ""Microsoft.KeyVault.Data"",
|
||||
""aliases"": [],
|
||||
""resourceTypeAliases"": [{
|
||||
""resourceType"": ""vaults/certificates"",
|
||||
""aliases"": [{
|
||||
""name"": ""Microsoft.KeyVault.Data/vaults/certificates/keySize"",
|
||||
""paths"": [{ ""path"": ""keySize"", ""apiVersions"": [""7.0""] }]
|
||||
}]
|
||||
}]
|
||||
}";
|
||||
|
||||
[TestMethod]
|
||||
public void Create_and_dispose_succeeds()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
Assert.AreEqual(0, registry.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoadJson_populates_registry()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
Assert.AreEqual(1, registry.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoadManifest_populates_registry()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadManifest(ManifestJson);
|
||||
Assert.AreEqual(1, registry.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NormalizeAndWrap_produces_envelope()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
|
||||
var resource = @"{
|
||||
""name"": ""acct1"",
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
|
||||
}";
|
||||
|
||||
var result = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var envelope = JsonNode.Parse(result!)!;
|
||||
Assert.IsNotNull(envelope["resource"]);
|
||||
Assert.IsNotNull(envelope["parameters"]);
|
||||
Assert.IsNotNull(envelope["context"]);
|
||||
|
||||
// Normalized resource should have lowercased alias field names
|
||||
var res = envelope["resource"]!;
|
||||
Assert.AreEqual(true, res["supportshttpstrafficonly"]?.GetValue<bool>());
|
||||
Assert.AreEqual("Hot", res["accesstier"]?.GetValue<string>());
|
||||
Assert.AreEqual("acct1", res["name"]?.GetValue<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NormalizeAndWrap_with_context_and_parameters()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
|
||||
var resource = @"{
|
||||
""name"": ""acct1"",
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""properties"": { ""supportsHttpsTrafficOnly"": true }
|
||||
}";
|
||||
var context = @"{""resourceGroup"": {""name"": ""rg1""}}";
|
||||
var parameters = @"{""env"": ""prod""}";
|
||||
|
||||
var result = registry.NormalizeAndWrap(resource, "2023-01-01", context, parameters);
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var envelope = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual("rg1", envelope["context"]!["resourceGroup"]!["name"]?.GetValue<string>());
|
||||
Assert.AreEqual("prod", envelope["parameters"]!["env"]?.GetValue<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Denormalize_restores_properties()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
|
||||
var normalized = @"{
|
||||
""name"": ""acct1"",
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""supportshttpstrafficonly"": true,
|
||||
""accesstier"": ""Hot""
|
||||
}";
|
||||
|
||||
var result = registry.Denormalize(normalized, "2023-01-01");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var arm = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
|
||||
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
|
||||
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Round_trip_normalize_then_denormalize()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
|
||||
var resource = @"{
|
||||
""name"": ""acct1"",
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
|
||||
}";
|
||||
|
||||
// Normalize
|
||||
var envelopeJson = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
|
||||
Assert.IsNotNull(envelopeJson);
|
||||
|
||||
var envelope = JsonNode.Parse(envelopeJson!)!;
|
||||
var normalizedResource = envelope["resource"]!.ToJsonString();
|
||||
|
||||
// Denormalize
|
||||
var armJson = registry.Denormalize(normalizedResource, "2023-01-01");
|
||||
Assert.IsNotNull(armJson);
|
||||
|
||||
var arm = JsonNode.Parse(armJson!)!;
|
||||
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
|
||||
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
|
||||
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DataPlane_manifest_normalize()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadManifest(ManifestJson);
|
||||
|
||||
var resource = @"{
|
||||
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
|
||||
""keySize"": 2048
|
||||
}";
|
||||
|
||||
var result = registry.NormalizeAndWrap(resource, "7.0", "{}", "{}");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var envelope = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual(2048, envelope["resource"]!["keysize"]?.GetValue<int>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void LoadJson_invalid_throws()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson("not valid json");
|
||||
}
|
||||
}
|
||||
131
bindings/csharp/Regorus.Tests/ExecutionTimerTests.cs
Normal file
131
bindings/csharp/Regorus.Tests/ExecutionTimerTests.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[DoNotParallelize] // Uses global fallback config; must run sequentially.
|
||||
[TestClass]
|
||||
public class ExecutionTimerTests
|
||||
{
|
||||
private const string Policy = @"
|
||||
package limits.timer
|
||||
import rego.v1
|
||||
|
||||
triplet_count := count([1 |
|
||||
x := data.values[_]
|
||||
y := data.values[_]
|
||||
z := data.values[_]
|
||||
])
|
||||
";
|
||||
|
||||
private const string Query = "data.limits.timer.triplet_count";
|
||||
private const int ValueCount = 160;
|
||||
|
||||
[TestMethod]
|
||||
public void Engine_limit_enforced()
|
||||
{
|
||||
Engine.ClearFallbackExecutionTimerConfig();
|
||||
using var engine = CreateEngine(ValueCount);
|
||||
var config = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
||||
engine.SetExecutionTimerConfig(config);
|
||||
|
||||
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
|
||||
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Fallback_applies_to_new_engines()
|
||||
{
|
||||
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
||||
Engine.SetFallbackExecutionTimerConfig(fallback);
|
||||
try
|
||||
{
|
||||
using var engine = CreateEngine(ValueCount);
|
||||
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
|
||||
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Engine.ClearFallbackExecutionTimerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Engine_override_relaxes_fallback()
|
||||
{
|
||||
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
||||
Engine.SetFallbackExecutionTimerConfig(fallback);
|
||||
try
|
||||
{
|
||||
using var engine = CreateEngine(ValueCount);
|
||||
var relaxed = new ExecutionTimerConfig(TimeSpan.FromSeconds(12), checkInterval: 1);
|
||||
engine.SetExecutionTimerConfig(relaxed);
|
||||
|
||||
var resultJson = engine.EvalRule(Query);
|
||||
var result = JsonSerializer.Deserialize<int>(resultJson!);
|
||||
Assert.IsTrue(result > 0, "Expected a positive triplet count when limit is relaxed.");
|
||||
|
||||
engine.ClearExecutionTimerConfig();
|
||||
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
|
||||
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Engine.ClearFallbackExecutionTimerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CompiledPolicy_limit_enforced()
|
||||
{
|
||||
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
||||
Engine.SetFallbackExecutionTimerConfig(fallback);
|
||||
try
|
||||
{
|
||||
using var policy = CreateCompiledPolicy(ValueCount);
|
||||
var ex = Assert.ThrowsException<InvalidOperationException>(() => policy.EvalWithInput("null"));
|
||||
StringAssert.Contains(ex.Message, "execution exceeded time limit");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Engine.ClearFallbackExecutionTimerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CompiledPolicy_uses_engine_limits_only()
|
||||
{
|
||||
// Compiled policies no longer store per-policy execution timers; limits are managed by Engine.
|
||||
Engine.ClearFallbackExecutionTimerConfig();
|
||||
using var policy = CreateCompiledPolicy(ValueCount);
|
||||
var resultJson = policy.EvalWithInput("null");
|
||||
var result = JsonSerializer.Deserialize<int>(resultJson!);
|
||||
Assert.IsTrue(result > 0, "CompiledPolicy should evaluate using engine defaults without its own timer");
|
||||
}
|
||||
|
||||
private static Engine CreateEngine(int valueCount)
|
||||
{
|
||||
var engine = new Engine();
|
||||
engine.AddPolicy("limits_timer.rego", Policy);
|
||||
engine.AddDataJson(CreateData(valueCount));
|
||||
return engine;
|
||||
}
|
||||
|
||||
private static CompiledPolicy CreateCompiledPolicy(int valueCount)
|
||||
{
|
||||
var modules = new[] { new PolicyModule("limits_timer.rego", Policy) };
|
||||
return Compiler.CompilePolicyWithEntrypoint(CreateData(valueCount), modules, Query);
|
||||
}
|
||||
|
||||
private static string CreateData(int valueCount)
|
||||
{
|
||||
var payload = new { values = Enumerable.Range(0, valueCount).ToArray() };
|
||||
return JsonSerializer.Serialize(payload);
|
||||
}
|
||||
}
|
||||
320
bindings/csharp/Regorus.Tests/MemoryGrowthTests.cs
Normal file
320
bindings/csharp/Regorus.Tests/MemoryGrowthTests.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
[DoNotParallelize]
|
||||
public class MemoryGrowthTests
|
||||
{
|
||||
private static int Iterations =>
|
||||
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_ITERS"), out var value) ? value : 50_000;
|
||||
|
||||
private static int LogEvery =>
|
||||
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_LOG_EVERY"), out var value) ? value : 500;
|
||||
|
||||
private static int GcEvery
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GC_EVERY"), out var value))
|
||||
{
|
||||
value = LogEvery;
|
||||
}
|
||||
|
||||
return value <= 0 ? LogEvery : value;
|
||||
}
|
||||
}
|
||||
|
||||
private static long? MaxWorkingSetDeltaBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!long.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_MAX_DELTA_MB"), out var mb))
|
||||
{
|
||||
mb = 32;
|
||||
}
|
||||
|
||||
if (mb <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return mb * 1024L * 1024L;
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong? GlobalRegorusMemoryLimitBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ulong.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GLOBAL_REGORUS_LIMIT_MB"), out var mb))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mb == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return mb * 1024UL * 1024UL;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WithOptionalGlobalRegorusMemoryLimit(Action action)
|
||||
{
|
||||
var priorLimit = MemoryLimits.GetGlobalMemoryLimit();
|
||||
try
|
||||
{
|
||||
if (GlobalRegorusMemoryLimitBytes is { } limit)
|
||||
{
|
||||
MemoryLimits.SetGlobalMemoryLimit(limit);
|
||||
}
|
||||
|
||||
action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryLimits.SetGlobalMemoryLimit(priorLimit);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ForceFullGc()
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Engine_create_eval_dispose_does_not_grow_working_set()
|
||||
{
|
||||
WithOptionalGlobalRegorusMemoryLimit(() =>
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
process.Refresh();
|
||||
var baseline = process.WorkingSet64;
|
||||
var maxDelta = 0L;
|
||||
var baselineManaged = GC.GetTotalMemory(false);
|
||||
var maxManagedDelta = 0L;
|
||||
|
||||
for (var i = 1; i <= Iterations; i++)
|
||||
{
|
||||
using (var engine = new Engine())
|
||||
{
|
||||
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
|
||||
_ = engine.EvalRule("data.test.message");
|
||||
}
|
||||
|
||||
if (i % LogEvery == 0)
|
||||
{
|
||||
process.Refresh();
|
||||
var workingSet = process.WorkingSet64;
|
||||
var managed = GC.GetTotalMemory(false);
|
||||
var delta = workingSet - baseline;
|
||||
var managedDelta = managed - baselineManaged;
|
||||
if (delta > maxDelta)
|
||||
{
|
||||
maxDelta = delta;
|
||||
}
|
||||
if (managedDelta > maxManagedDelta)
|
||||
{
|
||||
maxManagedDelta = managedDelta;
|
||||
}
|
||||
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (MaxWorkingSetDeltaBytes is { } limit)
|
||||
{
|
||||
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
||||
Assert.IsTrue(
|
||||
maxDelta <= limit,
|
||||
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Engine_create_eval_finalize_does_not_grow_working_set()
|
||||
{
|
||||
WithOptionalGlobalRegorusMemoryLimit(() =>
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
process.Refresh();
|
||||
var baseline = process.WorkingSet64;
|
||||
var maxDelta = 0L;
|
||||
var baselineManaged = GC.GetTotalMemory(false);
|
||||
var maxManagedDelta = 0L;
|
||||
|
||||
for (var i = 1; i <= Iterations; i++)
|
||||
{
|
||||
var engine = new Engine();
|
||||
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
|
||||
_ = engine.EvalRule("data.test.message");
|
||||
|
||||
if (i % GcEvery == 0)
|
||||
{
|
||||
ForceFullGc();
|
||||
}
|
||||
|
||||
if (i % LogEvery == 0)
|
||||
{
|
||||
process.Refresh();
|
||||
var workingSet = process.WorkingSet64;
|
||||
var managed = GC.GetTotalMemory(false);
|
||||
var delta = workingSet - baseline;
|
||||
var managedDelta = managed - baselineManaged;
|
||||
if (delta > maxDelta)
|
||||
{
|
||||
maxDelta = delta;
|
||||
}
|
||||
if (managedDelta > maxManagedDelta)
|
||||
{
|
||||
maxManagedDelta = managedDelta;
|
||||
}
|
||||
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (MaxWorkingSetDeltaBytes is { } limit)
|
||||
{
|
||||
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
||||
Assert.IsTrue(
|
||||
maxDelta <= limit,
|
||||
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Rvm_rehydrate_execute_dispose_does_not_grow_working_set()
|
||||
{
|
||||
WithOptionalGlobalRegorusMemoryLimit(() =>
|
||||
{
|
||||
var modules = new[]
|
||||
{
|
||||
new PolicyModule("test.rego", "package test\nallow = true"),
|
||||
};
|
||||
|
||||
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
|
||||
var serialized = compiled.SerializeBinary();
|
||||
|
||||
var process = Process.GetCurrentProcess();
|
||||
process.Refresh();
|
||||
var baseline = process.WorkingSet64;
|
||||
var maxDelta = 0L;
|
||||
var baselineManaged = GC.GetTotalMemory(false);
|
||||
var maxManagedDelta = 0L;
|
||||
|
||||
for (var i = 1; i <= Iterations; i++)
|
||||
{
|
||||
using (var vm = new Rvm())
|
||||
using (var program = Program.DeserializeBinary(serialized, out _))
|
||||
{
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson("{}");
|
||||
vm.SetInputJson("{}");
|
||||
_ = vm.ExecuteEntryPoint(0);
|
||||
}
|
||||
|
||||
if (i % LogEvery == 0)
|
||||
{
|
||||
process.Refresh();
|
||||
var workingSet = process.WorkingSet64;
|
||||
var managed = GC.GetTotalMemory(false);
|
||||
var delta = workingSet - baseline;
|
||||
var managedDelta = managed - baselineManaged;
|
||||
if (delta > maxDelta)
|
||||
{
|
||||
maxDelta = delta;
|
||||
}
|
||||
if (managedDelta > maxManagedDelta)
|
||||
{
|
||||
maxManagedDelta = managedDelta;
|
||||
}
|
||||
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (MaxWorkingSetDeltaBytes is { } limit)
|
||||
{
|
||||
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
||||
Assert.IsTrue(
|
||||
maxDelta <= limit,
|
||||
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Rvm_rehydrate_execute_finalize_does_not_grow_working_set()
|
||||
{
|
||||
WithOptionalGlobalRegorusMemoryLimit(() =>
|
||||
{
|
||||
var modules = new[]
|
||||
{
|
||||
new PolicyModule("test.rego", "package test\nallow = true"),
|
||||
};
|
||||
|
||||
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
|
||||
var serialized = compiled.SerializeBinary();
|
||||
|
||||
var process = Process.GetCurrentProcess();
|
||||
process.Refresh();
|
||||
var baseline = process.WorkingSet64;
|
||||
var maxDelta = 0L;
|
||||
var baselineManaged = GC.GetTotalMemory(false);
|
||||
var maxManagedDelta = 0L;
|
||||
|
||||
for (var i = 1; i <= Iterations; i++)
|
||||
{
|
||||
var vm = new Rvm();
|
||||
var program = Program.DeserializeBinary(serialized, out _);
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson("{}");
|
||||
vm.SetInputJson("{}");
|
||||
_ = vm.ExecuteEntryPoint(0);
|
||||
|
||||
if (i % GcEvery == 0)
|
||||
{
|
||||
ForceFullGc();
|
||||
}
|
||||
|
||||
if (i % LogEvery == 0)
|
||||
{
|
||||
process.Refresh();
|
||||
var workingSet = process.WorkingSet64;
|
||||
var managed = GC.GetTotalMemory(false);
|
||||
var delta = workingSet - baseline;
|
||||
var managedDelta = managed - baselineManaged;
|
||||
if (delta > maxDelta)
|
||||
{
|
||||
maxDelta = delta;
|
||||
}
|
||||
if (managedDelta > maxManagedDelta)
|
||||
{
|
||||
maxManagedDelta = managedDelta;
|
||||
}
|
||||
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (MaxWorkingSetDeltaBytes is { } limit)
|
||||
{
|
||||
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
|
||||
Assert.IsTrue(
|
||||
maxDelta <= limit,
|
||||
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
84
bindings/csharp/Regorus.Tests/PanicGuardTests.cs
Normal file
84
bindings/csharp/Regorus.Tests/PanicGuardTests.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
#if REGORUS_FFI_TEST_HOOKS
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus.Internal;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class PanicGuardTests
|
||||
{
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
API.regorus_engine_test_reset_poison();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
API.regorus_engine_test_reset_poison();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Panic_produces_invalid_operation_exception()
|
||||
{
|
||||
var panic = Assert.ThrowsException<InvalidOperationException>(TriggerPanic);
|
||||
StringAssert.Contains(panic.Message, "panicked", "panic message should capture payload");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Poison_flag_blocks_subsequent_calls()
|
||||
{
|
||||
_ = Assert.ThrowsException<InvalidOperationException>(TriggerPanic);
|
||||
var poisoned = Assert.ThrowsException<InvalidOperationException>(TriggerPanic);
|
||||
StringAssert.Contains(poisoned.Message, "poisoned", "poisoned message should explain guard state");
|
||||
}
|
||||
|
||||
private static unsafe void TriggerPanic()
|
||||
{
|
||||
var result = API.regorus_engine_test_trigger_panic();
|
||||
try
|
||||
{
|
||||
if (result.status == RegorusStatus.Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = PtrToStringUtf8((IntPtr)result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? PtrToStringUtf8(IntPtr ptr)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
return Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var len = 0;
|
||||
while (Marshal.ReadByte(ptr, len) != 0)
|
||||
{
|
||||
len++;
|
||||
}
|
||||
|
||||
var buffer = new byte[len];
|
||||
Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return System.Text.Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
374
bindings/csharp/Regorus.Tests/RbacEngineTests.cs
Normal file
374
bindings/csharp/Regorus.Tests/RbacEngineTests.cs
Normal file
@@ -0,0 +1,374 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class RbacEngineTests
|
||||
{
|
||||
public TestContext? TestContext { get; set; }
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
private const string BaseContextJson = """
|
||||
{
|
||||
"principal": {
|
||||
"id": "user-1",
|
||||
"principal_type": "User",
|
||||
"custom_security_attributes": {
|
||||
"department": "eng",
|
||||
"levels": ["L1", "L2"]
|
||||
}
|
||||
},
|
||||
"resource": {
|
||||
"id": "/subscriptions/s1",
|
||||
"resource_type": "Microsoft.Storage/storageAccounts",
|
||||
"scope": "/subscriptions/s1",
|
||||
"attributes": {
|
||||
"owner": "alice",
|
||||
"tags": ["a", "b"],
|
||||
"count": 5,
|
||||
"enabled": false,
|
||||
"ip": "10.0.0.5",
|
||||
"guid": "a1b2c3d4-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"action": "Microsoft.Storage/storageAccounts/read",
|
||||
"data_action": "Microsoft.Storage/storageAccounts/read",
|
||||
"attributes": {
|
||||
"owner": "alice",
|
||||
"text": "HelloWorld",
|
||||
"tags": ["prod", "gold"],
|
||||
"count": 10,
|
||||
"ratio": 2.5,
|
||||
"enabled": true,
|
||||
"ip": "10.0.0.8",
|
||||
"guid": "A1B2C3D4-0000-0000-0000-000000000000",
|
||||
"time": "12:30:15",
|
||||
"date": "2023-05-01T12:00:00Z",
|
||||
"numbers": [1, 2, 3],
|
||||
"letters": ["a", "b"]
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"is_private_link": false,
|
||||
"private_endpoint": null,
|
||||
"subnet": null,
|
||||
"utc_now": "2023-05-01T12:00:00Z"
|
||||
},
|
||||
"action": "Microsoft.Storage/storageAccounts/read",
|
||||
"suboperation": "sub/read"
|
||||
}
|
||||
""";
|
||||
|
||||
[TestMethod]
|
||||
public void Rbac_engine_evaluates_all_yaml_cases()
|
||||
{
|
||||
var cases = LoadEvalTestCases().ToList();
|
||||
Assert.IsTrue(cases.Count > 0, "No RBAC test cases were loaded.");
|
||||
|
||||
foreach (var testCase in cases)
|
||||
{
|
||||
TestContext?.WriteLine($"RBAC case: {testCase.Name} -> {testCase.Condition}");
|
||||
var context = BuildBaseContext();
|
||||
if (testCase.Context != null)
|
||||
{
|
||||
ApplyOverrides(context, testCase.Context);
|
||||
}
|
||||
|
||||
var contextJson = context.ToJsonString(JsonOptions);
|
||||
var result = RbacEngine.EvaluateCondition(testCase.Condition, contextJson);
|
||||
|
||||
Assert.AreEqual(
|
||||
testCase.Expected,
|
||||
result,
|
||||
$"RBAC test '{testCase.Name}' failed for condition '{testCase.Condition}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonObject BuildBaseContext()
|
||||
{
|
||||
var node = JsonNode.Parse(BaseContextJson) as JsonObject;
|
||||
if (node is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to parse base context JSON.");
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static void ApplyOverrides(JsonObject context, EvalContextOverrides overrides)
|
||||
{
|
||||
var principal = (JsonObject?)context["principal"]
|
||||
?? throw new InvalidOperationException("Missing principal section.");
|
||||
var resource = (JsonObject?)context["resource"]
|
||||
?? throw new InvalidOperationException("Missing resource section.");
|
||||
var request = (JsonObject?)context["request"]
|
||||
?? throw new InvalidOperationException("Missing request section.");
|
||||
var environment = (JsonObject?)context["environment"]
|
||||
?? throw new InvalidOperationException("Missing environment section.");
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.Action))
|
||||
{
|
||||
context["action"] = overrides.Action;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.Suboperation))
|
||||
{
|
||||
context["suboperation"] = overrides.Suboperation;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.RequestAction))
|
||||
{
|
||||
request["action"] = overrides.RequestAction;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.DataAction))
|
||||
{
|
||||
request["data_action"] = overrides.DataAction;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.PrincipalId))
|
||||
{
|
||||
principal["id"] = overrides.PrincipalId;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.PrincipalType))
|
||||
{
|
||||
principal["principal_type"] = overrides.PrincipalType;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.ResourceId))
|
||||
{
|
||||
resource["id"] = overrides.ResourceId;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.ResourceType))
|
||||
{
|
||||
resource["resource_type"] = overrides.ResourceType;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.ResourceScope))
|
||||
{
|
||||
resource["scope"] = overrides.ResourceScope;
|
||||
}
|
||||
|
||||
if (overrides.RequestAttributes != null)
|
||||
{
|
||||
request["attributes"] = ConvertToJsonNode(overrides.RequestAttributes);
|
||||
}
|
||||
|
||||
if (overrides.ResourceAttributes != null)
|
||||
{
|
||||
resource["attributes"] = ConvertToJsonNode(overrides.ResourceAttributes);
|
||||
}
|
||||
|
||||
if (overrides.PrincipalCustomSecurityAttributes != null)
|
||||
{
|
||||
principal["custom_security_attributes"] = ConvertToJsonNode(overrides.PrincipalCustomSecurityAttributes);
|
||||
}
|
||||
|
||||
if (overrides.Environment != null)
|
||||
{
|
||||
if (overrides.Environment.IsPrivateLink.HasValue)
|
||||
{
|
||||
environment["is_private_link"] = overrides.Environment.IsPrivateLink.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.Environment.PrivateEndpoint))
|
||||
{
|
||||
environment["private_endpoint"] = overrides.Environment.PrivateEndpoint;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.Environment.Subnet))
|
||||
{
|
||||
environment["subnet"] = overrides.Environment.Subnet;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(overrides.Environment.UtcNow))
|
||||
{
|
||||
environment["utc_now"] = overrides.Environment.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<EvalTestCase> LoadEvalTestCases()
|
||||
{
|
||||
var baseDir = Path.Combine(AppContext.BaseDirectory, "test_cases");
|
||||
if (!Directory.Exists(baseDir))
|
||||
{
|
||||
throw new DirectoryNotFoundException($"RBAC test case directory not found: {baseDir}");
|
||||
}
|
||||
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
|
||||
var files = Directory.EnumerateFiles(baseDir, "*.yaml")
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var yaml = File.ReadAllText(file);
|
||||
var suite = deserializer.Deserialize<EvalTestSuite>(yaml);
|
||||
if (suite?.TestCases is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var testCase in suite.TestCases)
|
||||
{
|
||||
yield return testCase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonNode? ConvertToJsonNode(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case JsonNode node:
|
||||
return node;
|
||||
case string text:
|
||||
return JsonValue.Create(text);
|
||||
case bool boolean:
|
||||
return JsonValue.Create(boolean);
|
||||
case int intValue:
|
||||
return JsonValue.Create(intValue);
|
||||
case long longValue:
|
||||
return JsonValue.Create(longValue);
|
||||
case double doubleValue:
|
||||
return JsonValue.Create(doubleValue);
|
||||
case float floatValue:
|
||||
return JsonValue.Create(floatValue);
|
||||
case decimal decimalValue:
|
||||
return JsonValue.Create(decimalValue);
|
||||
case DateTime dateTime:
|
||||
return JsonValue.Create(dateTime.ToString("O"));
|
||||
case IDictionary dictionary:
|
||||
{
|
||||
var obj = new JsonObject();
|
||||
foreach (DictionaryEntry entry in dictionary)
|
||||
{
|
||||
var key = entry.Key?.ToString() ?? string.Empty;
|
||||
obj[key] = ConvertToJsonNode(entry.Value);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
case IEnumerable enumerable:
|
||||
{
|
||||
if (value is string)
|
||||
{
|
||||
return JsonValue.Create(value.ToString());
|
||||
}
|
||||
|
||||
var array = new JsonArray();
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
array.Add(ConvertToJsonNode(item));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
default:
|
||||
return JsonValue.Create(value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EvalTestSuite
|
||||
{
|
||||
[YamlMember(Alias = "test_cases")]
|
||||
public List<EvalTestCase> TestCases { get; set; } = new();
|
||||
}
|
||||
|
||||
private sealed class EvalTestCase
|
||||
{
|
||||
[YamlMember(Alias = "name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember(Alias = "condition")]
|
||||
public string Condition { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember(Alias = "expected")]
|
||||
public bool Expected { get; set; }
|
||||
|
||||
[YamlMember(Alias = "context")]
|
||||
public EvalContextOverrides? Context { get; set; }
|
||||
}
|
||||
|
||||
private sealed class EvalContextOverrides
|
||||
{
|
||||
[YamlMember(Alias = "action")]
|
||||
public string? Action { get; set; }
|
||||
|
||||
[YamlMember(Alias = "suboperation")]
|
||||
public string? Suboperation { get; set; }
|
||||
|
||||
[YamlMember(Alias = "request_action")]
|
||||
public string? RequestAction { get; set; }
|
||||
|
||||
[YamlMember(Alias = "data_action")]
|
||||
public string? DataAction { get; set; }
|
||||
|
||||
[YamlMember(Alias = "principal_id")]
|
||||
public string? PrincipalId { get; set; }
|
||||
|
||||
[YamlMember(Alias = "principal_type")]
|
||||
public string? PrincipalType { get; set; }
|
||||
|
||||
[YamlMember(Alias = "resource_id")]
|
||||
public string? ResourceId { get; set; }
|
||||
|
||||
[YamlMember(Alias = "resource_type")]
|
||||
public string? ResourceType { get; set; }
|
||||
|
||||
[YamlMember(Alias = "resource_scope")]
|
||||
public string? ResourceScope { get; set; }
|
||||
|
||||
[YamlMember(Alias = "request_attributes")]
|
||||
public object? RequestAttributes { get; set; }
|
||||
|
||||
[YamlMember(Alias = "resource_attributes")]
|
||||
public object? ResourceAttributes { get; set; }
|
||||
|
||||
[YamlMember(Alias = "principal_custom_security_attributes")]
|
||||
public object? PrincipalCustomSecurityAttributes { get; set; }
|
||||
|
||||
[YamlMember(Alias = "environment")]
|
||||
public EvalEnvironmentOverrides? Environment { get; set; }
|
||||
}
|
||||
|
||||
private sealed class EvalEnvironmentOverrides
|
||||
{
|
||||
[YamlMember(Alias = "is_private_link")]
|
||||
public bool? IsPrivateLink { get; set; }
|
||||
|
||||
[YamlMember(Alias = "private_endpoint")]
|
||||
public string? PrivateEndpoint { get; set; }
|
||||
|
||||
[YamlMember(Alias = "subnet")]
|
||||
public string? Subnet { get; set; }
|
||||
|
||||
[YamlMember(Alias = "utc_now")]
|
||||
public string? UtcNow { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
<!-- More info about dotnet test integration https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test -->
|
||||
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
|
||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -18,10 +19,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" Version="3.8.2" />
|
||||
<PackageReference Include="MSTest" />
|
||||
<PackageReference Include="YamlDotNet" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
|
||||
<PackageReference Include="Regorus" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../../src/languages/azure_rbac/test_cases/*.yaml" Link="test_cases/%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus;
|
||||
@@ -11,6 +12,8 @@ namespace Regorus.Tests;
|
||||
[TestClass]
|
||||
public class RegorusTests
|
||||
{
|
||||
private static readonly object LimitLock = new();
|
||||
|
||||
[TestMethod]
|
||||
public void Basic_evaluation_succeeds()
|
||||
{
|
||||
@@ -190,10 +193,19 @@ public class RegorusTests
|
||||
|
||||
var result = engine.GetPolicyPackageNames();
|
||||
|
||||
var packageNames = JsonNode.Parse(result!);
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual("test", packageNames![0]["package_name"].ToString());
|
||||
Assert.AreEqual("test.nested.name", packageNames![1]["package_name"].ToString());
|
||||
var packageNames = JsonNode.Parse(result);
|
||||
Assert.IsNotNull(packageNames);
|
||||
|
||||
var packageArray = packageNames.AsArray();
|
||||
var firstPackage = packageArray[0]?.AsObject();
|
||||
var secondPackage = packageArray[1]?.AsObject();
|
||||
|
||||
Assert.IsNotNull(firstPackage);
|
||||
Assert.IsNotNull(secondPackage);
|
||||
Assert.AreEqual("test", firstPackage!["package_name"]!.GetValue<string>());
|
||||
Assert.AreEqual("test.nested.name", secondPackage!["package_name"]!.GetValue<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -206,51 +218,251 @@ public class RegorusTests
|
||||
|
||||
var result = engine.GetPolicyParameters();
|
||||
|
||||
var parameters = JsonNode.Parse(result!);
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(1, parameters![0]["parameters"].AsArray().Count);
|
||||
Assert.AreEqual(1, parameters![0]["modifiers"].AsArray().Count);
|
||||
var parameters = JsonNode.Parse(result);
|
||||
Assert.IsNotNull(parameters);
|
||||
|
||||
Assert.AreEqual("a", parameters![0]["parameters"][0]["name"].ToString());
|
||||
Assert.AreEqual("b", parameters![0]["modifiers"][0]["name"].ToString());
|
||||
var parametersArray = parameters.AsArray();
|
||||
var firstEntry = parametersArray[0]?.AsObject();
|
||||
Assert.IsNotNull(firstEntry);
|
||||
|
||||
var parameterList = firstEntry!["parameters"]!.AsArray();
|
||||
var modifierList = firstEntry["modifiers"]!.AsArray();
|
||||
|
||||
Assert.AreEqual(1, parameterList.Count);
|
||||
Assert.AreEqual(1, modifierList.Count);
|
||||
|
||||
var parameterName = parameterList[0]?.AsObject()?["name"]?.GetValue<string>();
|
||||
var modifierName = modifierList[0]?.AsObject()?["name"]?.GetValue<string>();
|
||||
|
||||
Assert.AreEqual("a", parameterName);
|
||||
Assert.AreEqual("b", modifierName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetInputJson_has_negligible_allocations_after_warmup()
|
||||
{
|
||||
using var engine = new Engine();
|
||||
const string payload = "{}";
|
||||
|
||||
// Warm up the engine and JIT to ensure subsequent measurements are representative.
|
||||
for (int i = 0; i < 16; i++)
|
||||
[TestMethod]
|
||||
public void Global_memory_limit_can_be_set_and_cleared()
|
||||
{
|
||||
engine.SetInputJson(payload);
|
||||
lock (LimitLock)
|
||||
{
|
||||
using var guard = new MemoryLimitScope();
|
||||
|
||||
MemoryLimits.SetGlobalMemoryLimit(null);
|
||||
Assert.IsNull(MemoryLimits.GetGlobalMemoryLimit());
|
||||
|
||||
const ulong limit = 32 * 1024;
|
||||
MemoryLimits.SetGlobalMemoryLimit(limit);
|
||||
Assert.AreEqual(limit, MemoryLimits.GetGlobalMemoryLimit());
|
||||
|
||||
MemoryLimits.SetGlobalMemoryLimit(null);
|
||||
Assert.IsNull(MemoryLimits.GetGlobalMemoryLimit());
|
||||
}
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
const int iterations = 256;
|
||||
var before = GC.GetAllocatedBytesForCurrentThread();
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
[TestMethod]
|
||||
public void Memory_limit_violations_surface_from_engine_calls()
|
||||
{
|
||||
engine.SetInputJson(payload);
|
||||
lock (LimitLock)
|
||||
{
|
||||
using var guard = new MemoryLimitScope();
|
||||
using var engine = new Engine();
|
||||
|
||||
const ulong limit = 1;
|
||||
var payload = new string('x', 128 * 1024);
|
||||
|
||||
MemoryLimits.FlushThreadMemoryCounters();
|
||||
MemoryLimits.SetGlobalMemoryLimit(limit);
|
||||
|
||||
try
|
||||
{
|
||||
var ex = Assert.ThrowsException<InvalidOperationException>(
|
||||
() => engine.SetInputJson($"{{\"payload\":\"{payload}\"}}"));
|
||||
StringAssert.Contains(ex.Message, "execution exceeded memory limit");
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryLimits.SetGlobalMemoryLimit(null);
|
||||
MemoryLimits.FlushThreadMemoryCounters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var after = GC.GetAllocatedBytesForCurrentThread();
|
||||
var allocated = Math.Max(0, after - before);
|
||||
var bytesPerOp = allocated / (double)iterations;
|
||||
[TestMethod]
|
||||
public void Evaluation_fails_when_input_pushes_policy_over_global_limit()
|
||||
{
|
||||
lock (LimitLock)
|
||||
{
|
||||
using var guard = new MemoryLimitScope();
|
||||
using var engine = new Engine();
|
||||
|
||||
// Runtime bookkeeping (delegate caches, GC write barriers) differs across platforms, so
|
||||
// we measure bytes per call rather than absolute totals and allow a small budget.
|
||||
// CI will flag regressions where marshalling starts allocating per invocation.
|
||||
const string policy = """
|
||||
package memorylimit
|
||||
|
||||
// Allow a small budget for delegates and runtime bookkeeping while still flagging regressions.
|
||||
Assert.IsTrue(
|
||||
bytesPerOp <= 512,
|
||||
$"Expected ≤512 B/op after warmup, but observed {bytesPerOp:F2} B/op (total {allocated} bytes)."
|
||||
);
|
||||
}
|
||||
import rego.v1
|
||||
|
||||
stretched := concat("", [input.block | numbers.range(0, input.repeat - 1)[_]])
|
||||
""";
|
||||
|
||||
engine.AddPolicy("memorylimit.rego", policy);
|
||||
|
||||
MemoryLimits.FlushThreadMemoryCounters();
|
||||
const ulong limit = 4 * 1024 * 1024;
|
||||
MemoryLimits.SetGlobalMemoryLimit(limit);
|
||||
|
||||
var block = new string('x', 16 * 1024);
|
||||
|
||||
var smallInput = JsonSerializer.Serialize(new { block, repeat = 16 });
|
||||
engine.SetInputJson(smallInput);
|
||||
var smallResult = engine.EvalRule("data.memorylimit.stretched");
|
||||
Assert.IsNotNull(smallResult);
|
||||
var stretched = JsonSerializer.Deserialize<string>(smallResult);
|
||||
Assert.IsNotNull(stretched, "Policy should return a string result.");
|
||||
Assert.AreEqual(block.Length * 16, stretched!.Length, "Policy should expand the payload under the limit.");
|
||||
|
||||
var largeInput = JsonSerializer.Serialize(new { block, repeat = 4096 });
|
||||
engine.SetInputJson(largeInput);
|
||||
|
||||
var ex = Assert.ThrowsException<InvalidOperationException>(
|
||||
() => engine.EvalRule("data.memorylimit.stretched"));
|
||||
StringAssert.Contains(ex.Message, "execution exceeded memory limit");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Thread_flush_threshold_roundtrips()
|
||||
{
|
||||
lock (LimitLock)
|
||||
{
|
||||
var original = MemoryLimits.GetThreadMemoryFlushThreshold();
|
||||
try
|
||||
{
|
||||
const ulong threshold = 256 * 1024;
|
||||
MemoryLimits.SetThreadFlushThresholdOverride(threshold);
|
||||
Assert.AreEqual(threshold, MemoryLimits.GetThreadMemoryFlushThreshold());
|
||||
|
||||
MemoryLimits.SetThreadFlushThresholdOverride(null);
|
||||
var restored = MemoryLimits.GetThreadMemoryFlushThreshold();
|
||||
Assert.IsTrue(restored.HasValue, "Clearing override should restore allocator default.");
|
||||
if (original.HasValue)
|
||||
{
|
||||
Assert.AreEqual(original, restored);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryLimits.SetThreadFlushThresholdOverride(original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetInputJson_has_negligible_allocations_after_warmup()
|
||||
{
|
||||
using var engine = new Engine();
|
||||
const string payload = "{}";
|
||||
|
||||
// Warm up the engine and JIT to ensure subsequent measurements are representative.
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
engine.SetInputJson(payload);
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
const int iterations = 256;
|
||||
var before = GC.GetAllocatedBytesForCurrentThread();
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
engine.SetInputJson(payload);
|
||||
}
|
||||
|
||||
var after = GC.GetAllocatedBytesForCurrentThread();
|
||||
var allocated = Math.Max(0, after - before);
|
||||
var bytesPerOp = allocated / (double)iterations;
|
||||
|
||||
// Runtime bookkeeping (delegate caches, GC write barriers) differs across platforms, so
|
||||
// we measure bytes per call rather than absolute totals and allow a small budget.
|
||||
// CI will flag regressions where marshalling starts allocating per invocation.
|
||||
|
||||
// Allow a small budget for delegates and runtime bookkeeping while still flagging regressions.
|
||||
Assert.IsTrue(
|
||||
bytesPerOp <= 512,
|
||||
$"Expected ≤512 B/op after warmup, but observed {bytesPerOp:F2} B/op (total {allocated} bytes)."
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Disposed_objects_throw_object_disposed_exception()
|
||||
{
|
||||
var engine = new Engine();
|
||||
engine.Dispose();
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => engine.EvalRule("data.test.message"));
|
||||
|
||||
var program = Program.CreateEmpty();
|
||||
program.Dispose();
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => program.SerializeBinary());
|
||||
|
||||
var rvm = new Rvm();
|
||||
rvm.Dispose();
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => rvm.Execute());
|
||||
|
||||
var modules = new[] { new PolicyModule("test.rego", "package test\nallow = true") };
|
||||
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.test.allow");
|
||||
compiled.Dispose();
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => compiled.EvalWithInput("{}"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Registry_helpers_return_empty_after_clear()
|
||||
{
|
||||
TargetRegistry.Clear();
|
||||
Assert.IsTrue(TargetRegistry.IsEmpty);
|
||||
Assert.AreEqual(0, TargetRegistry.GetNames().Count);
|
||||
|
||||
SchemaRegistry.ClearResources();
|
||||
SchemaRegistry.ClearEffects();
|
||||
Assert.IsTrue(SchemaRegistry.IsResourceRegistryEmpty);
|
||||
Assert.IsTrue(SchemaRegistry.IsEffectRegistryEmpty);
|
||||
Assert.AreEqual(0, SchemaRegistry.GetResourceNames().Count);
|
||||
Assert.AreEqual(0, SchemaRegistry.GetEffectNames().Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Utf8_marshalling_handles_large_unicode_payloads()
|
||||
{
|
||||
var payload = string.Concat(new string('ß', 2048), "-✓-", new string('漢', 1024));
|
||||
|
||||
using var engine = new Engine();
|
||||
engine.AddPolicy("test.rego", "package test\nmessage = input.msg");
|
||||
engine.SetInputJson(JsonSerializer.Serialize(new { msg = payload }));
|
||||
|
||||
var result = engine.EvalRule("data.test.message");
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
// Compare by parsing the JSON string to avoid encoder differences across platforms.
|
||||
var parsed = JsonSerializer.Deserialize<string>(result);
|
||||
Assert.IsNotNull(parsed);
|
||||
|
||||
Assert.AreEqual(payload, parsed);
|
||||
}
|
||||
|
||||
private sealed class MemoryLimitScope : IDisposable
|
||||
{
|
||||
private readonly ulong? _originalLimit;
|
||||
|
||||
public MemoryLimitScope()
|
||||
{
|
||||
_originalLimit = MemoryLimits.GetGlobalMemoryLimit();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
MemoryLimits.SetGlobalMemoryLimit(_originalLimit);
|
||||
MemoryLimits.FlushThreadMemoryCounters();
|
||||
}
|
||||
}
|
||||
}
|
||||
119
bindings/csharp/Regorus.Tests/RvmProgramTests.cs
Normal file
119
bindings/csharp/Regorus.Tests/RvmProgramTests.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class RvmProgramTests
|
||||
{
|
||||
private const string Policy = """
|
||||
package demo
|
||||
default allow = false
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
some role in data.roles[input.user]
|
||||
role == "admin"
|
||||
count(input.actions) > 0
|
||||
}
|
||||
""";
|
||||
|
||||
private const string Data = """
|
||||
{
|
||||
"roles": {
|
||||
"alice": ["admin", "reader"]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string Input = """
|
||||
{
|
||||
"user": "alice",
|
||||
"actions": ["read"]
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HostAwaitPolicy = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HostAwaitInput = """
|
||||
{
|
||||
"account": {
|
||||
"id": "acct-1",
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[TestMethod]
|
||||
public void Program_compile_and_execute_succeeds()
|
||||
{
|
||||
var modules = new[] { new PolicyModule("demo.rego", Policy) };
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
var program = Program.CompileFromModules(Data, modules, entryPoints);
|
||||
var listing = program.GenerateListing();
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(listing), "listing should be generated");
|
||||
|
||||
var binary = program.SerializeBinary();
|
||||
var rehydrated = Program.DeserializeBinary(binary, out var isPartial);
|
||||
Assert.IsFalse(isPartial, "program should be fully deserialized");
|
||||
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(rehydrated);
|
||||
vm.SetDataJson(Data);
|
||||
vm.SetInputJson(Input);
|
||||
|
||||
var result = vm.Execute();
|
||||
Assert.AreEqual("true", result, "expected allow=true");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Program_compile_from_engine_succeeds()
|
||||
{
|
||||
using var engine = new Engine();
|
||||
engine.AddPolicy("demo.rego", Policy);
|
||||
|
||||
var program = Program.CompileFromEngine(engine, new[] { "data.demo.allow" });
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson(Data);
|
||||
vm.SetInputJson(Input);
|
||||
|
||||
var result = vm.Execute();
|
||||
Assert.AreEqual("true", result, "expected allow=true");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Program_host_await_suspend_and_resume_succeeds()
|
||||
{
|
||||
var modules = new[] { new PolicyModule("host_await.rego", HostAwaitPolicy) };
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Program.CompileFromModules("{}", modules, entryPoints);
|
||||
using var vm = new Rvm();
|
||||
vm.SetExecutionMode(1);
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(HostAwaitInput);
|
||||
|
||||
var initial = vm.Execute();
|
||||
var state = vm.GetExecutionState();
|
||||
Assert.IsNotNull(state, "execution state should be available");
|
||||
StringAssert.Contains(state!, "HostAwait", "expected HostAwait suspension");
|
||||
|
||||
var resumed = vm.Resume("{\"tier\":\"gold\"}");
|
||||
Assert.AreEqual("true", resumed, "expected allow=true after resume");
|
||||
}
|
||||
}
|
||||
153
bindings/csharp/Regorus/AliasRegistry.cs
Normal file
153
bindings/csharp/Regorus/AliasRegistry.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages Azure Policy alias definitions used for resource normalization
|
||||
/// and policy compilation.
|
||||
/// </summary>
|
||||
public unsafe sealed class AliasRegistry : SafeHandleWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an empty alias registry.
|
||||
/// </summary>
|
||||
public AliasRegistry()
|
||||
: base(RegorusAliasRegistryHandle.Create(), nameof(AliasRegistry))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">JSON array of ProviderAliases (e.g. from Get-AzPolicyAlias or ResourceTypesAndAliases.json)</param>
|
||||
public void LoadJson(string json)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||
{
|
||||
UseHandle(regPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_alias_registry_load_json(
|
||||
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a data-plane policy manifest from a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">JSON object containing a DataPolicyManifest</param>
|
||||
public void LoadManifest(string json)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||
{
|
||||
UseHandle(regPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_alias_registry_load_manifest(
|
||||
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of resource types loaded in the registry.
|
||||
/// </summary>
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return UseHandle(regPtr =>
|
||||
{
|
||||
return ResultHelpers.GetIntResult(
|
||||
API.regorus_alias_registry_len((RegorusAliasRegistry*)regPtr));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
|
||||
/// expected by a compiled Azure Policy program.
|
||||
/// </summary>
|
||||
/// <param name="resourceJson">Raw ARM resource JSON</param>
|
||||
/// <param name="apiVersion">API version string (e.g. "2023-01-01"), or null to use default alias paths</param>
|
||||
/// <param name="contextJson">Additional context JSON object (pass "{}" if none)</param>
|
||||
/// <param name="parametersJson">Policy parameter values JSON (pass "{}" if none)</param>
|
||||
/// <returns>JSON string: { "resource": <normalized>, "context": <context>, "parameters": <params> }</returns>
|
||||
public string? NormalizeAndWrap(string resourceJson, string? apiVersion = null, string contextJson = "{}", string parametersJson = "{}")
|
||||
{
|
||||
return Utf8Marshaller.WithUtf8(resourceJson, resPtr =>
|
||||
Utf8Marshaller.WithUtf8(contextJson, ctxPtr =>
|
||||
Utf8Marshaller.WithUtf8(parametersJson, paramsPtr =>
|
||||
{
|
||||
if (apiVersion is null)
|
||||
{
|
||||
return UseHandle(regPtr =>
|
||||
{
|
||||
return ResultHelpers.GetStringResult(
|
||||
API.regorus_alias_registry_normalize_and_wrap(
|
||||
(RegorusAliasRegistry*)regPtr,
|
||||
(byte*)resPtr, null,
|
||||
(byte*)ctxPtr, (byte*)paramsPtr));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
||||
UseHandle(regPtr =>
|
||||
{
|
||||
return ResultHelpers.GetStringResult(
|
||||
API.regorus_alias_registry_normalize_and_wrap(
|
||||
(RegorusAliasRegistry*)regPtr,
|
||||
(byte*)resPtr, (byte*)apiPtr,
|
||||
(byte*)ctxPtr, (byte*)paramsPtr));
|
||||
}));
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||
/// </summary>
|
||||
/// <param name="normalizedJson">The normalized resource JSON</param>
|
||||
/// <param name="apiVersion">API version string, or null to use default alias paths</param>
|
||||
/// <returns>Denormalized ARM JSON string</returns>
|
||||
public string? Denormalize(string normalizedJson, string? apiVersion = null)
|
||||
{
|
||||
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
|
||||
{
|
||||
if (apiVersion is null)
|
||||
{
|
||||
return UseHandle(regPtr =>
|
||||
{
|
||||
return ResultHelpers.GetStringResult(
|
||||
API.regorus_alias_registry_denormalize(
|
||||
(RegorusAliasRegistry*)regPtr,
|
||||
(byte*)normPtr, null));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
||||
UseHandle(regPtr =>
|
||||
{
|
||||
return ResultHelpers.GetStringResult(
|
||||
API.regorus_alias_registry_denormalize(
|
||||
(RegorusAliasRegistry*)regPtr,
|
||||
(byte*)normPtr, (byte*)apiPtr));
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(RegorusResult result)
|
||||
{
|
||||
return ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
bindings/csharp/Regorus/AssemblyInfo.cs
Normal file
3
bindings/csharp/Regorus/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Regorus.Tests")]
|
||||
39
bindings/csharp/Regorus/CacheConfig.cs
Normal file
39
bindings/csharp/Regorus/CacheConfig.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Global configuration for compiled pattern caches used by regex and glob builtins.
|
||||
/// </summary>
|
||||
public readonly struct CacheConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CacheConfig"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="regex">Maximum cached compiled regex patterns (default 256, 0 = disabled).</param>
|
||||
/// <param name="glob">Maximum cached compiled glob matchers (default 128, 0 = disabled).</param>
|
||||
public CacheConfig(nuint regex, nuint glob)
|
||||
{
|
||||
Regex = regex;
|
||||
Glob = glob;
|
||||
}
|
||||
|
||||
/// <summary>Maximum cached compiled regex patterns (default 256).</summary>
|
||||
public nuint Regex { get; }
|
||||
|
||||
/// <summary>Maximum cached compiled glob matchers (default 128).</summary>
|
||||
public nuint Glob { get; }
|
||||
|
||||
internal Regorus.Internal.RegorusCacheConfig ToNative()
|
||||
{
|
||||
return new Regorus.Internal.RegorusCacheConfig
|
||||
{
|
||||
regex = Regex,
|
||||
glob = Glob,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,8 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
@@ -18,20 +17,15 @@ namespace Regorus
|
||||
/// Each instance represents a unique native policy object.
|
||||
///
|
||||
/// Thread Safety: This class is thread-safe for all operations. Multiple threads
|
||||
/// can safely call EvalWithInput() concurrently, and Dispose() will safely wait
|
||||
/// for all active evaluations to complete before freeing resources. No external
|
||||
/// synchronization is required.
|
||||
/// can safely call EvalWithInput() concurrently. Dispose() blocks new calls, waits
|
||||
/// briefly, and defers the native release to the last in-flight caller if needed.
|
||||
/// No external synchronization is required.
|
||||
/// </summary>
|
||||
public unsafe sealed class CompiledPolicy : IDisposable
|
||||
public unsafe sealed class CompiledPolicy : SafeHandleWrapper
|
||||
{
|
||||
private RegorusCompiledPolicyHandle? _handle;
|
||||
private readonly ManualResetEventSlim _idleEvent = new(initialState: true);
|
||||
private int _isDisposed;
|
||||
private int _activeEvaluations;
|
||||
|
||||
internal CompiledPolicy(RegorusCompiledPolicyHandle handle)
|
||||
: base(handle, nameof(CompiledPolicy))
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,36 +39,16 @@ namespace Regorus
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
|
||||
public string? EvalWithInput(string inputJson)
|
||||
{
|
||||
// Increment active evaluations count
|
||||
var active = System.Threading.Interlocked.Increment(ref _activeEvaluations);
|
||||
if (active == 1)
|
||||
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
{
|
||||
_idleEvent.Reset();
|
||||
}
|
||||
try
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
return UseHandle(policyPtr =>
|
||||
{
|
||||
return UseHandle(policyPtr =>
|
||||
unsafe
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Decrement active evaluations count
|
||||
var remaining = System.Threading.Interlocked.Decrement(ref _activeEvaluations);
|
||||
if (remaining == 0)
|
||||
{
|
||||
_idleEvent.Set();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +60,6 @@ namespace Regorus
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
|
||||
public PolicyInfo GetPolicyInfo()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var jsonResult = UseHandle(policyPtr =>
|
||||
{
|
||||
unsafe
|
||||
@@ -94,7 +67,7 @@ namespace Regorus
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info((Internal.RegorusCompiledPolicy*)policyPtr));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(jsonResult))
|
||||
{
|
||||
throw new Exception("Failed to get policy info: empty response");
|
||||
@@ -106,8 +79,8 @@ namespace Regorus
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
|
||||
|
||||
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
|
||||
?? throw new Exception("Failed to deserialize policy info");
|
||||
}
|
||||
catch (JsonException ex)
|
||||
@@ -116,105 +89,9 @@ namespace Regorus
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle != null)
|
||||
{
|
||||
_idleEvent.Wait();
|
||||
|
||||
handle.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
|
||||
_idleEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
|
||||
private string? StringFromUTF8(IntPtr ptr)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
int len = 0;
|
||||
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
|
||||
byte[] buffer = new byte[len];
|
||||
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
private string? CheckAndDropResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
|
||||
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
Internal.RegorusDataType.Integer => result.int_value.ToString(),
|
||||
Internal.RegorusDataType.None => null,
|
||||
_ => StringFromUTF8((IntPtr)result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private RegorusCompiledPolicyHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
return Internal.ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
@@ -14,17 +12,17 @@ namespace Regorus
|
||||
/// <summary>
|
||||
/// Represents a policy module with an ID and content.
|
||||
/// </summary>
|
||||
public struct PolicyModule
|
||||
public readonly struct PolicyModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for this policy module.
|
||||
/// Gets the unique identifier for this policy module.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Rego policy content.
|
||||
/// Gets the Rego policy content.
|
||||
/// </summary>
|
||||
public string Content { get; set; }
|
||||
public string Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the PolicyModule struct.
|
||||
@@ -55,50 +53,40 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when compilation fails</exception>
|
||||
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IEnumerable<PolicyModule> modules, string entryPointRule)
|
||||
{
|
||||
var modulesArray = modules.ToArray();
|
||||
|
||||
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
|
||||
|
||||
try
|
||||
if (modules is null)
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
return CompilePolicyWithEntrypoint(dataJson, modules.ToArray(), entryPointRule);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles a policy from data and modules with a specific entry point rule.
|
||||
/// </summary>
|
||||
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IReadOnlyList<PolicyModule> modules, string entryPointRule)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
using var pinnedModules = Internal.ModuleMarshalling.PinPolicyModules(modules);
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
|
||||
nativeModules[i] = new Internal.RegorusPolicyModule
|
||||
unsafe
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
|
||||
{
|
||||
unsafe
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
|
||||
{
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_with_entrypoint(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length, (byte*)entryPointPtr);
|
||||
var result = Internal.API.regorus_compile_policy_with_entrypoint(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)pinnedModules.Length, (byte*)entryPointPtr);
|
||||
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
return GetCompiledPolicyResult(result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,62 +100,39 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when compilation fails</exception>
|
||||
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IEnumerable<PolicyModule> modules)
|
||||
{
|
||||
var modulesArray = modules.ToArray();
|
||||
|
||||
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
|
||||
|
||||
try
|
||||
if (modules is null)
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
|
||||
nativeModules[i] = new Internal.RegorusPolicyModule
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_for_target(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length);
|
||||
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
pinned.Dispose();
|
||||
}
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
return CompilePolicyForTarget(dataJson, modules.ToArray());
|
||||
}
|
||||
|
||||
private static string? StringFromUTF8(IntPtr ptr)
|
||||
/// <summary>
|
||||
/// Compiles a target-aware policy from data and modules.
|
||||
/// </summary>
|
||||
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IReadOnlyList<PolicyModule> modules)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
int len = 0;
|
||||
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
|
||||
byte[] buffer = new byte[len];
|
||||
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
using var pinnedModules = Internal.ModuleMarshalling.PinPolicyModules(modules);
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_for_target(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)pinnedModules.Length);
|
||||
|
||||
return GetCompiledPolicyResult(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CompiledPolicy GetCompiledPolicyResult(Internal.RegorusResult result)
|
||||
@@ -176,8 +141,8 @@ namespace Regorus
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown compilation error occurred");
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != Internal.RegorusDataType.Pointer || result.pointer_value == null)
|
||||
|
||||
@@ -16,52 +16,42 @@ namespace Regorus
|
||||
/// Cloning is cheap and involves only incrementing reference counts for shared immutable objects like parsed policies,
|
||||
/// data etc. Mutable state is deep copied as needed.
|
||||
/// </summary>
|
||||
public unsafe sealed class Engine : IDisposable
|
||||
public unsafe sealed class Engine : SafeHandleWrapper
|
||||
{
|
||||
private RegorusEngineHandle? _handle;
|
||||
private int _isDisposed;
|
||||
|
||||
public Engine()
|
||||
: base(RegorusEngineHandle.Create(), nameof(Engine))
|
||||
{
|
||||
_handle = RegorusEngineHandle.Create();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public static void SetFallbackExecutionTimerConfig(ExecutionTimerConfig config)
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
|
||||
// This object will be cleaned up by the Dispose method.
|
||||
// Therefore, call GC.SuppressFinalize to
|
||||
// take this object off the finalization queue
|
||||
// and prevent finalization code for this object
|
||||
// from executing a second time.
|
||||
GC.SuppressFinalize(this);
|
||||
var nativeConfig = config.ToNative();
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_set_fallback_execution_timer_config(nativeConfig));
|
||||
}
|
||||
|
||||
// Dispose(bool disposing) executes in two distinct scenarios.
|
||||
// If disposing equals true, the method has been called directly
|
||||
// or indirectly by a user's code. Managed and unmanaged resources
|
||||
// can be disposed.
|
||||
// If disposing equals false, the method has been called by the
|
||||
// runtime from inside the finalizer and you should not reference
|
||||
// other objects. Only unmanaged resources can be disposed.
|
||||
void Dispose(bool disposing)
|
||||
public static void ClearFallbackExecutionTimerConfig()
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_clear_fallback_execution_timer_config());
|
||||
}
|
||||
|
||||
public static void SetCacheConfig(CacheConfig config)
|
||||
{
|
||||
var nativeConfig = config.ToNative();
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_set_cache_config(nativeConfig));
|
||||
}
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_clear_cache());
|
||||
}
|
||||
|
||||
private Engine(RegorusEngineHandle handle)
|
||||
: base(handle, nameof(Engine))
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
public Engine Clone()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
@@ -80,365 +70,216 @@ namespace Regorus
|
||||
|
||||
public void SetStrictBuiltinErrors(bool strict)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
|
||||
});
|
||||
}
|
||||
|
||||
public void SetExecutionTimerConfig(ExecutionTimerConfig config)
|
||||
{
|
||||
var nativeConfig = config.ToNative();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
var localConfig = nativeConfig;
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr, &localConfig));
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearExecutionTimerConfig()
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public void SetPolicyLengthConfig(PolicyLengthConfig config)
|
||||
{
|
||||
var nativeConfig = config.ToNative();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_policy_length_config((Regorus.Internal.RegorusEngine*)enginePtr, nativeConfig));
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearPolicyLengthConfig()
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_policy_length_config((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? AddPolicy(string path, string rego)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
Utf8Marshaller.WithUtf8(rego, regoPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr))
|
||||
)));
|
||||
}
|
||||
|
||||
public void SetRegoV0(bool enable)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
});
|
||||
}
|
||||
|
||||
public string? AddPolicyFromFile(string path)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
return UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr))
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(data, dataPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(input, inputPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public string? EvalQuery(string query)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(query, queryPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
return UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public string? EvalRule(string rule)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(rule, rulePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
return UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public void SetEnableCoverage(bool enable)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearCoverageData()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetCoverageReport()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetCoverageReportPretty()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public void SetGatherPrints(bool enable)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
});
|
||||
}
|
||||
|
||||
public string? TakePrints()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetAstAsJson()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetPolicyPackageNames()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetPolicyParameters()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
string? StringFromUTF8(IntPtr ptr)
|
||||
private static string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
|
||||
{
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
int len = 0;
|
||||
while (Marshal.ReadByte(ptr, len) != 0) { ++len; }
|
||||
byte[] buffer = new byte[len];
|
||||
Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
|
||||
{
|
||||
if (result.status != Regorus.Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
Regorus.Internal.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
var resultString = "";
|
||||
if (result.output is not null)
|
||||
{
|
||||
resultString = StringFromUTF8((IntPtr)result.output);
|
||||
}
|
||||
Regorus.Internal.API.regorus_result_drop(result);
|
||||
return resultString;
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
}
|
||||
|
||||
private RegorusEngineHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
UseHandle<object?>(handlePtr =>
|
||||
{
|
||||
action(handlePtr);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
return ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
62
bindings/csharp/Regorus/ExecutionTimerConfig.cs
Normal file
62
bindings/csharp/Regorus/ExecutionTimerConfig.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Managed representation of the execution timer configuration used by the engine.
|
||||
/// </summary>
|
||||
public readonly struct ExecutionTimerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExecutionTimerConfig"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="limit">Maximum wall-clock duration allowed for evaluation. Must be non-negative.</param>
|
||||
/// <param name="checkInterval">Number of work units between timer checks. Must be non-zero.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="limit"/> is negative or <paramref name="checkInterval"/> is zero.</exception>
|
||||
public ExecutionTimerConfig(TimeSpan limit, uint checkInterval)
|
||||
{
|
||||
if (limit < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(limit), "Execution timer limit must be non-negative.");
|
||||
}
|
||||
|
||||
if (checkInterval == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(checkInterval), "Execution timer check interval must be non-zero.");
|
||||
}
|
||||
|
||||
Limit = limit;
|
||||
CheckInterval = checkInterval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum wall-clock duration allowed for an evaluation.
|
||||
/// </summary>
|
||||
public TimeSpan Limit { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of work units between timer checks.
|
||||
/// </summary>
|
||||
public uint CheckInterval { get; }
|
||||
|
||||
internal Regorus.Internal.RegorusExecutionTimerConfig ToNative()
|
||||
{
|
||||
if (Limit < TimeSpan.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Execution timer limit must be non-negative.");
|
||||
}
|
||||
|
||||
ulong ticks = checked((ulong)Limit.Ticks);
|
||||
ulong limitNanoseconds = checked(ticks * 100UL);
|
||||
|
||||
return new Regorus.Internal.RegorusExecutionTimerConfig
|
||||
{
|
||||
limit_ns = limitNanoseconds,
|
||||
check_interval = CheckInterval,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
128
bindings/csharp/Regorus/MemoryLimits.cs
Normal file
128
bindings/csharp/Regorus/MemoryLimits.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for configuring and inspecting Regorus memory limits via the native allocator bridge.
|
||||
/// </summary>
|
||||
public static class MemoryLimits
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure the process-wide global memory limit in bytes. Pass <c>null</c> to remove the limit.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Maximum number of bytes the allocator may reserve before signalling an error.</param>
|
||||
public static void SetGlobalMemoryLimit(ulong? bytes)
|
||||
{
|
||||
var result = API.regorus_set_global_memory_limit(bytes ?? 0, bytes.HasValue);
|
||||
EnsureSuccess(result, nameof(SetGlobalMemoryLimit));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently configured global memory limit, if any.
|
||||
/// </summary>
|
||||
public static ulong? GetGlobalMemoryLimit()
|
||||
{
|
||||
var result = API.regorus_get_global_memory_limit();
|
||||
return ExtractOptionalU64(result, "Failed to get global memory limit");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the allocator to flush this thread's pending counters into the global aggregates.
|
||||
/// </summary>
|
||||
public static void FlushThreadMemoryCounters()
|
||||
{
|
||||
var result = API.regorus_flush_thread_memory_counters();
|
||||
EnsureSuccess(result, nameof(FlushThreadMemoryCounters));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately checks the global memory limit and throws if the allocator reports exhaustion.
|
||||
/// </summary>
|
||||
public static void CheckGlobalMemoryLimit()
|
||||
{
|
||||
var result = API.regorus_check_global_memory_limit();
|
||||
EnsureSuccess(result, nameof(CheckGlobalMemoryLimit));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override the per-thread automatic flush threshold in bytes. Pass <c>null</c> to restore the default.
|
||||
/// </summary>
|
||||
public static void SetThreadFlushThresholdOverride(ulong? bytes)
|
||||
{
|
||||
var result = API.regorus_set_thread_flush_threshold_override(bytes ?? 0, bytes.HasValue);
|
||||
EnsureSuccess(result, nameof(SetThreadFlushThresholdOverride));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the per-thread flush threshold, if automatic flushing is enabled.
|
||||
/// </summary>
|
||||
public static ulong? GetThreadMemoryFlushThreshold()
|
||||
{
|
||||
var result = API.regorus_get_thread_memory_flush_threshold();
|
||||
return ExtractOptionalU64(result, "Failed to get thread memory flush threshold");
|
||||
}
|
||||
|
||||
private static unsafe ulong? ExtractOptionalU64(RegorusResult result, string errorContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message) ?? $"{errorContext}: native call failed";
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (!result.bool_value)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Integer)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{errorContext}: native call returned {result.data_type} ({(int)result.data_type}) with bool_value={result.bool_value}"
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return checked((ulong)result.int_value);
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
throw new OverflowException($"{errorContext}: native value was out of range ({result.int_value})", ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureSuccess(RegorusResult result, string operation)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
string? message;
|
||||
unsafe
|
||||
{
|
||||
message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
}
|
||||
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
bindings/csharp/Regorus/ModuleMarshalling.cs
Normal file
156
bindings/csharp/Regorus/ModuleMarshalling.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using Regorus;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Regorus.Internal
|
||||
{
|
||||
internal static unsafe class ModuleMarshalling
|
||||
{
|
||||
internal sealed class PinnedPolicyModules : IDisposable
|
||||
{
|
||||
private readonly List<Utf8Marshaller.PinnedUtf8> _pins;
|
||||
private bool _disposed;
|
||||
|
||||
internal PinnedPolicyModules(RegorusPolicyModule[] buffer, int length, List<Utf8Marshaller.PinnedUtf8> pins)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Length = length;
|
||||
_pins = pins;
|
||||
}
|
||||
|
||||
internal RegorusPolicyModule[] Buffer { get; }
|
||||
|
||||
internal int Length { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pin in _pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<RegorusPolicyModule>.Shared.Return(Buffer, clearArray: true);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PinnedEntryPoints : IDisposable
|
||||
{
|
||||
private readonly List<Utf8Marshaller.PinnedUtf8> _pins;
|
||||
private bool _disposed;
|
||||
|
||||
internal PinnedEntryPoints(IntPtr[] buffer, int length, List<Utf8Marshaller.PinnedUtf8> pins)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Length = length;
|
||||
_pins = pins;
|
||||
}
|
||||
|
||||
internal IntPtr[] Buffer { get; }
|
||||
|
||||
internal int Length { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pin in _pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<IntPtr>.Shared.Return(Buffer, clearArray: true);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PinnedPolicyModules PinPolicyModules(IReadOnlyList<PolicyModule> modules)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
var count = modules.Count;
|
||||
var buffer = ArrayPool<RegorusPolicyModule>.Shared.Rent(count);
|
||||
var pins = new List<Utf8Marshaller.PinnedUtf8>(count * 2);
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modules[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modules[i].Content);
|
||||
pins.Add(idPinned);
|
||||
pins.Add(contentPinned);
|
||||
|
||||
buffer[i] = new RegorusPolicyModule
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
return new PinnedPolicyModules(buffer, count, pins);
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (var pin in pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<RegorusPolicyModule>.Shared.Return(buffer, clearArray: true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PinnedEntryPoints PinEntryPoints(IReadOnlyList<string> entryPoints)
|
||||
{
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
var count = entryPoints.Count;
|
||||
var buffer = ArrayPool<IntPtr>.Shared.Rent(count);
|
||||
var pins = new List<Utf8Marshaller.PinnedUtf8>(count);
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var entryPinned = Utf8Marshaller.Pin(entryPoints[i]);
|
||||
pins.Add(entryPinned);
|
||||
buffer[i] = (IntPtr)entryPinned.Pointer;
|
||||
}
|
||||
|
||||
return new PinnedEntryPoints(buffer, count, pins);
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (var pin in pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<IntPtr>.Shared.Return(buffer, clearArray: true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,53 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_result_drop(RegorusResult result);
|
||||
|
||||
/// <summary>
|
||||
/// Drop a RegorusBuffer.
|
||||
/// data is not valid after drop.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_buffer_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_buffer_drop(RegorusBuffer* buffer);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Memory Limit Methods
|
||||
|
||||
/// <summary>
|
||||
/// Set the global memory limit. Pass hasLimit=false to clear the limit.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_set_global_memory_limit", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_set_global_memory_limit(ulong limit, [MarshalAs(UnmanagedType.U1)] bool hasLimit);
|
||||
|
||||
/// <summary>
|
||||
/// Get the current global memory limit. bool_value indicates whether a limit is set.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_get_global_memory_limit", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_get_global_memory_limit();
|
||||
|
||||
/// <summary>
|
||||
/// Check the global memory limit immediately.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_check_global_memory_limit", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_check_global_memory_limit();
|
||||
|
||||
/// <summary>
|
||||
/// Flush the current thread's pending allocation counters into global aggregates.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_flush_thread_memory_counters", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_flush_thread_memory_counters();
|
||||
|
||||
/// <summary>
|
||||
/// Set the per-thread flush threshold override. Pass hasThreshold=false to restore defaults.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_set_thread_flush_threshold_override", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_set_thread_flush_threshold_override(ulong threshold, [MarshalAs(UnmanagedType.U1)] bool hasThreshold);
|
||||
|
||||
/// <summary>
|
||||
/// Get the per-thread flush threshold. bool_value indicates whether a threshold is configured.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_get_thread_memory_flush_threshold", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_get_thread_memory_flush_threshold();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Engine Methods
|
||||
@@ -45,6 +92,12 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from the engine state with entry points.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_program_with_entrypoints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_compile_program_with_entrypoints(RegorusEngine* engine, byte** entryPoints, UIntPtr entryPointsLen);
|
||||
|
||||
/// <summary>
|
||||
/// Drop a RegorusEngine.
|
||||
/// </summary>
|
||||
@@ -52,6 +105,138 @@ namespace Regorus.Internal
|
||||
internal static extern void regorus_engine_drop(RegorusEngine* engine);
|
||||
|
||||
/// <summary>
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from data/modules and entry points.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_compile_from_modules", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_compile_from_modules(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len, byte** entry_points, UIntPtr entry_points_len);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new empty program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusProgram* regorus_program_new();
|
||||
|
||||
/// <summary>
|
||||
/// Drop a program handle.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_program_drop(RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a program to binary format.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_serialize_binary", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_serialize_binary(RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize a program from binary format.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_deserialize_binary", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_deserialize_binary(byte* data, UIntPtr len, byte* is_partial);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a readable assembly listing for the program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_program_generate_listing", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_program_generate_listing(RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new RVM instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusRvm* regorus_rvm_new();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new RVM instance from a compiled policy.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_new_with_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_new_with_policy(RegorusCompiledPolicy* compiled_policy);
|
||||
|
||||
/// <summary>
|
||||
/// Drop an RVM instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_rvm_drop(RegorusRvm* vm);
|
||||
|
||||
/// <summary>
|
||||
/// Load a program into the RVM.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_load_program", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_load_program(RegorusRvm* vm, RegorusProgram* program);
|
||||
|
||||
/// <summary>
|
||||
/// Set the data document for the RVM.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_data(RegorusRvm* vm, byte* data_json);
|
||||
|
||||
/// <summary>
|
||||
/// Set the input document for the RVM.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_input(RegorusRvm* vm, byte* input_json);
|
||||
|
||||
/// <summary>
|
||||
/// Execute the program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_execute", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_execute(RegorusRvm* vm);
|
||||
|
||||
/// <summary>
|
||||
/// Execute an entry point by name.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_execute_entry_point_by_name", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_execute_entry_point_by_name(RegorusRvm* vm, byte* entry_point);
|
||||
|
||||
/// <summary>
|
||||
/// Execute an entry point by index.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_execute_entry_point_by_index", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_execute_entry_point_by_index(RegorusRvm* vm, UIntPtr index);
|
||||
|
||||
/// <summary>
|
||||
/// Resume execution.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_resume", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_resume(RegorusRvm* vm, byte* resume_value_json, [MarshalAs(UnmanagedType.I1)] bool has_value);
|
||||
|
||||
/// <summary>
|
||||
/// Get the current execution state.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_get_execution_state", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_get_execution_state(RegorusRvm* vm);
|
||||
|
||||
/// <summary>
|
||||
/// Set the maximum instruction limit.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_max_instructions", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_max_instructions(RegorusRvm* vm, UIntPtr max_instructions);
|
||||
|
||||
/// <summary>
|
||||
/// Set strict builtin error handling.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_strict_builtin_errors", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_strict_builtin_errors(RegorusRvm* vm, [MarshalAs(UnmanagedType.I1)] bool strict);
|
||||
|
||||
/// <summary>
|
||||
/// Set execution mode (0 run-to-completion, 1 suspendable).
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_execution_mode", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_execution_mode(RegorusRvm* vm, byte mode);
|
||||
|
||||
/// <summary>
|
||||
/// Set step mode for suspendable execution.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_step_mode", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_step_mode(RegorusRvm* vm, [MarshalAs(UnmanagedType.I1)] bool enabled);
|
||||
|
||||
/// <summary>
|
||||
/// Set execution timer configuration.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_execution_timer_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_execution_timer_config(RegorusRvm* vm, [MarshalAs(UnmanagedType.I1)] bool has_config, RegorusExecutionTimerConfig config);
|
||||
/// Add a policy.
|
||||
/// The policy is parsed into AST.
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
|
||||
@@ -217,6 +402,76 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_with_entrypoint", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_compile_with_entrypoint(RegorusEngine* engine, byte* rule);
|
||||
|
||||
#if REGORUS_FFI_TEST_HOOKS
|
||||
/// <summary>
|
||||
/// Trigger a panic inside the engine for testing purposes.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_test_trigger_panic", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_test_trigger_panic();
|
||||
|
||||
/// <summary>
|
||||
/// Reset the engine poison flag for testing.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_test_reset_poison", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_engine_test_reset_poison();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Configure the execution timer for a specific engine instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_execution_timer_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_set_execution_timer_config(RegorusEngine* engine, RegorusExecutionTimerConfig* config);
|
||||
|
||||
/// <summary>
|
||||
/// Clear the execution timer configuration for a specific engine instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_execution_timer_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_clear_execution_timer_config(RegorusEngine* engine);
|
||||
|
||||
/// <summary>
|
||||
/// Set the policy length limits for a specific engine instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_policy_length_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_set_policy_length_config(RegorusEngine* engine, RegorusPolicyLengthConfig config);
|
||||
|
||||
/// <summary>
|
||||
/// Clear the policy length configuration for a specific engine instance.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_policy_length_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_engine_clear_policy_length_config(RegorusEngine* engine);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Execution Timer Global Methods
|
||||
|
||||
/// <summary>
|
||||
/// Set the process-wide fallback execution timer configuration.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_set_fallback_execution_timer_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_set_fallback_execution_timer_config(RegorusExecutionTimerConfig config);
|
||||
|
||||
/// <summary>
|
||||
/// Clear the process-wide fallback execution timer configuration.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_clear_fallback_execution_timer_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_clear_fallback_execution_timer_config();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cache Configuration Global Methods
|
||||
|
||||
/// <summary>
|
||||
/// Configure the global pattern caches used by regex and glob builtins.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_set_cache_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_set_cache_config(RegorusCacheConfig config);
|
||||
|
||||
/// <summary>
|
||||
/// Clear all entries from every pattern cache.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_clear_cache", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_clear_cache();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compilation Methods
|
||||
@@ -265,6 +520,16 @@ namespace Regorus.Internal
|
||||
|
||||
#endregion
|
||||
|
||||
#region RBAC Methods
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate an Azure RBAC condition expression against a JSON evaluation context.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rbac_engine_eval_condition", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rbac_engine_eval_condition(byte* condition, byte* context_json);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Target Registry Methods
|
||||
|
||||
/// <summary>
|
||||
@@ -404,6 +669,55 @@ namespace Regorus.Internal
|
||||
internal static extern RegorusResult regorus_effect_schema_clear();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Alias Registry Methods
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty AliasRegistry.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusAliasRegistry* regorus_alias_registry_new();
|
||||
|
||||
/// <summary>
|
||||
/// Drop an AliasRegistry.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_alias_registry_drop(RegorusAliasRegistry* registry);
|
||||
|
||||
/// <summary>
|
||||
/// Load control-plane alias data (array of ProviderAliases) into the registry.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_load_json(RegorusAliasRegistry* registry, byte* json);
|
||||
|
||||
/// <summary>
|
||||
/// Load a data-plane policy manifest into the registry.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_load_manifest(RegorusAliasRegistry* registry, byte* json);
|
||||
|
||||
/// <summary>
|
||||
/// Return the number of resource types loaded in the alias registry.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_len(RegorusAliasRegistry* registry);
|
||||
|
||||
/// <summary>
|
||||
/// Normalize an ARM resource JSON and wrap it into the standard input envelope.
|
||||
/// Returns a JSON string.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_normalize_and_wrap", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_normalize_and_wrap(
|
||||
RegorusAliasRegistry* registry, byte* resource_json, byte* api_version, byte* context_json, byte* parameters_json);
|
||||
|
||||
/// <summary>
|
||||
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_denormalize", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_denormalize(
|
||||
RegorusAliasRegistry* registry, byte* normalized_json, byte* api_version);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Native Structures
|
||||
@@ -472,6 +786,14 @@ namespace Regorus.Internal
|
||||
/// Invalid policy content.
|
||||
/// </summary>
|
||||
InvalidPolicy,
|
||||
/// <summary>
|
||||
/// The engine panicked and cannot be reused until reset.
|
||||
/// </summary>
|
||||
Panic,
|
||||
/// <summary>
|
||||
/// The engine remains poisoned because a previous panic was detected.
|
||||
/// </summary>
|
||||
Poisoned,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -498,6 +820,7 @@ namespace Regorus.Internal
|
||||
/// Boolean value.
|
||||
/// Valid when data_type is Boolean.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool bool_value;
|
||||
/// <summary>
|
||||
/// Integer value.
|
||||
@@ -516,6 +839,48 @@ namespace Regorus.Internal
|
||||
public byte* error_message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FFI representation of the execution timer configuration.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct RegorusExecutionTimerConfig
|
||||
{
|
||||
public ulong limit_ns;
|
||||
public uint check_interval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FFI representation of the policy length configuration.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct RegorusPolicyLengthConfig
|
||||
{
|
||||
public uint max_col;
|
||||
public UIntPtr max_file_bytes;
|
||||
public UIntPtr max_lines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FFI representation of the cache configuration.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct RegorusCacheConfig
|
||||
{
|
||||
public UIntPtr regex;
|
||||
public UIntPtr glob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Byte buffer returned from FFI.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe struct RegorusBuffer
|
||||
{
|
||||
public byte* data;
|
||||
public UIntPtr len;
|
||||
public UIntPtr capacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for regorus::Engine.
|
||||
/// </summary>
|
||||
@@ -532,6 +897,22 @@ namespace Regorus.Internal
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for regorus::rvm::Program.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe partial struct RegorusProgram
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for regorus::rvm::RegoVM.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe partial struct RegorusRvm
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FFI wrapper for PolicyModule struct.
|
||||
/// </summary>
|
||||
@@ -542,5 +923,13 @@ namespace Regorus.Internal
|
||||
public byte* content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for AliasRegistry.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe partial struct RegorusAliasRegistry
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
53
bindings/csharp/Regorus/PolicyLengthConfig.cs
Normal file
53
bindings/csharp/Regorus/PolicyLengthConfig.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Policy source length limits enforced when loading policy files.
|
||||
/// </summary>
|
||||
public readonly struct PolicyLengthConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PolicyLengthConfig"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="maxCol">Maximum column width per line. Must be non-zero.</param>
|
||||
/// <param name="maxFileBytes">Maximum policy file size in bytes. Must be non-zero.</param>
|
||||
/// <param name="maxLines">Maximum number of lines per policy file. Must be non-zero.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any parameter is zero.</exception>
|
||||
public PolicyLengthConfig(uint maxCol, nuint maxFileBytes, nuint maxLines)
|
||||
{
|
||||
if (maxCol == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxCol), "Must be non-zero.");
|
||||
if (maxFileBytes == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxFileBytes), "Must be non-zero.");
|
||||
if (maxLines == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxLines), "Must be non-zero.");
|
||||
|
||||
MaxCol = maxCol;
|
||||
MaxFileBytes = maxFileBytes;
|
||||
MaxLines = maxLines;
|
||||
}
|
||||
|
||||
/// <summary>Maximum column width per line (default: 1024).</summary>
|
||||
public uint MaxCol { get; }
|
||||
|
||||
/// <summary>Maximum policy file size in bytes (default: 1 MiB).</summary>
|
||||
public nuint MaxFileBytes { get; }
|
||||
|
||||
/// <summary>Maximum number of lines per policy file (default: 20000).</summary>
|
||||
public nuint MaxLines { get; }
|
||||
|
||||
internal Regorus.Internal.RegorusPolicyLengthConfig ToNative()
|
||||
{
|
||||
return new Regorus.Internal.RegorusPolicyLengthConfig
|
||||
{
|
||||
max_col = MaxCol,
|
||||
max_file_bytes = MaxFileBytes,
|
||||
max_lines = MaxLines,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
249
bindings/csharp/Regorus/Program.cs
Normal file
249
bindings/csharp/Regorus/Program.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a compiled RVM program.
|
||||
/// </summary>
|
||||
public unsafe sealed class Program : SafeHandleWrapper
|
||||
{
|
||||
private Program(RegorusProgramHandle handle)
|
||||
: base(handle, nameof(Program))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty program.
|
||||
/// </summary>
|
||||
public static Program CreateEmpty()
|
||||
{
|
||||
return new Program(RegorusProgramHandle.Create());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from modules and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromModules(string dataJson, IEnumerable<PolicyModule> modules, IEnumerable<string> entryPoints)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
return CompileFromModules(dataJson, modules.ToArray(), entryPoints.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from modules and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromModules(string dataJson, IReadOnlyList<PolicyModule> modules, IReadOnlyList<string> entryPoints)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
if (entryPoints.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
|
||||
}
|
||||
|
||||
using var pinnedModules = ModuleMarshalling.PinPolicyModules(modules);
|
||||
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
fixed (RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
|
||||
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
|
||||
{
|
||||
var result = API.regorus_program_compile_from_modules(
|
||||
(byte*)dataPtr,
|
||||
modulesPtr,
|
||||
(UIntPtr)pinnedModules.Length,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)pinnedEntryPoints.Length);
|
||||
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from an engine instance and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromEngine(Engine engine, IEnumerable<string> entryPoints)
|
||||
{
|
||||
if (engine is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(engine));
|
||||
}
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
return CompileFromEngine(engine, entryPoints.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from an engine instance and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromEngine(Engine engine, IReadOnlyList<string> entryPoints)
|
||||
{
|
||||
if (engine is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(engine));
|
||||
}
|
||||
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
if (entryPoints.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
|
||||
}
|
||||
|
||||
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
|
||||
|
||||
return engine.UseHandleForInterop(enginePtr =>
|
||||
{
|
||||
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
|
||||
{
|
||||
var result = API.regorus_engine_compile_program_with_entrypoints(
|
||||
(RegorusEngine*)enginePtr,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)pinnedEntryPoints.Length);
|
||||
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize an RVM program from binary format.
|
||||
/// </summary>
|
||||
public static Program DeserializeBinary(byte[] data, out bool isPartial)
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
byte partialFlag = 0;
|
||||
fixed (byte* dataPtr = data)
|
||||
{
|
||||
var result = API.regorus_program_deserialize_binary(dataPtr, (UIntPtr)data.Length, &partialFlag);
|
||||
var program = GetProgramResult(result);
|
||||
isPartial = partialFlag != 0;
|
||||
return program;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize the program to binary format.
|
||||
/// </summary>
|
||||
public byte[] SerializeBinary()
|
||||
{
|
||||
return UseHandle(programPtr =>
|
||||
{
|
||||
var result = API.regorus_program_serialize_binary((RegorusProgram*)programPtr);
|
||||
return ExtractBuffer(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a readable assembly listing.
|
||||
/// </summary>
|
||||
public string? GenerateListing()
|
||||
{
|
||||
return UseHandle(programPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_program_generate_listing((RegorusProgram*)programPtr));
|
||||
});
|
||||
}
|
||||
|
||||
private static Program GetProgramResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new Exception("Expected program pointer but got different data type");
|
||||
}
|
||||
|
||||
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
|
||||
return new Program(handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(RegorusResult result)
|
||||
{
|
||||
return ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
|
||||
private static byte[] ExtractBuffer(RegorusResult result)
|
||||
{
|
||||
RegorusBuffer* buffer = null;
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new Exception("Expected buffer pointer but got different data type");
|
||||
}
|
||||
|
||||
buffer = (RegorusBuffer*)result.pointer_value;
|
||||
var length = checked((int)buffer->len);
|
||||
var data = new byte[length];
|
||||
if (length > 0)
|
||||
{
|
||||
Marshal.Copy((IntPtr)buffer->data, data, 0, length);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
API.regorus_buffer_drop(buffer);
|
||||
}
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
bindings/csharp/Regorus/RbacEngine.cs
Normal file
45
bindings/csharp/Regorus/RbacEngine.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helpers for evaluating Azure RBAC condition expressions.
|
||||
/// </summary>
|
||||
public static unsafe class RbacEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluate an Azure RBAC condition expression against a JSON evaluation context.
|
||||
/// </summary>
|
||||
/// <param name="condition">Azure RBAC condition expression.</param>
|
||||
/// <param name="contextJson">JSON encoded EvaluationContext.</param>
|
||||
/// <returns>True if the condition evaluates to true; otherwise false.</returns>
|
||||
/// <exception cref="Exception">Thrown when evaluation fails.</exception>
|
||||
public static bool EvaluateCondition(string condition, string contextJson)
|
||||
{
|
||||
if (condition is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(condition));
|
||||
}
|
||||
|
||||
if (contextJson is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contextJson));
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(condition, conditionPtr =>
|
||||
Utf8Marshaller.WithUtf8(contextJson, contextPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_rbac_engine_eval_condition((byte*)conditionPtr, (byte*)contextPtr);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,22 +8,39 @@
|
||||
<LangVersion>10.0</LangVersion>
|
||||
|
||||
<!-- See https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-pack -->
|
||||
<VersionPrefix>0.8.0</VersionPrefix>
|
||||
<VersionPrefix>0.9.1</VersionPrefix>
|
||||
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageLicenseExpression>MIT AND Apache-2.0 AND BSD-3-Clause</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/microsoft/regorus</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/microsoft/regorus</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<Authors>Microsoft</Authors>
|
||||
<Company>Microsoft</Company>
|
||||
<PackageTags>rego;policy;engine;authorization;opa;rust</PackageTags>
|
||||
<Description>Fast, lightweight Rego interpreter and policy engine for .NET, powered by Rust.</Description>
|
||||
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RegorusFFIArtifactsProfile Condition="'$(RegorusFFIArtifactsProfile)' == ''">release</RegorusFFIArtifactsProfile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(EnableRegorusTestHooks)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);REGORUS_FFI_TEST_HOOKS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
$(RegorusFFIArtifactsDir) is the location where regorus shared libraries have been
|
||||
built for various platforms and copied to. RegorusFFIArtifactsDir is passed in
|
||||
by the publishing pipeline.
|
||||
|
||||
For each target triple, `Pack` expects the regorus ffi shared library
|
||||
to be found in $(RegorusFFIArtifactsDir)/<target-triple>/release.
|
||||
to be found in $(RegorusFFIArtifactsDir)/<target-triple>/$(RegorusFFIArtifactsProfile).
|
||||
|
||||
If $(IgnoreMissingArtifacts) is not set, ensure that the binaries for officially supported platforms exists.
|
||||
-->
|
||||
@@ -31,27 +48,31 @@
|
||||
<Error Text="RegorusFFIArtifactsDir must be supplied." Condition="$(RegorusFFIArtifactsDir) == ''" />
|
||||
|
||||
<!-- Ensure that the binaries for officially supported platforms exists. -->
|
||||
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.dll missing."
|
||||
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.dll')" />
|
||||
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.pdb missing."
|
||||
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.pdb')" />
|
||||
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/regorus_ffi.dll missing."
|
||||
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/regorus_ffi.dll')" />
|
||||
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/regorus_ffi.pdb missing."
|
||||
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/regorus_ffi.pdb')" />
|
||||
|
||||
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/libregorus_ffi.so missing."
|
||||
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/libregorus_ffi.so')" />
|
||||
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/$(RegorusFFIArtifactsProfile)/libregorus_ffi.so missing."
|
||||
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/$(RegorusFFIArtifactsProfile)/libregorus_ffi.so')" />
|
||||
|
||||
<Error Text="$(RegorusFFIArtifactsDir)/aarch64-apple-darwin/$(RegorusFFIArtifactsProfile)/libregorus_ffi.dylib missing."
|
||||
Condition="!Exists('$(RegorusFFIArtifactsDir)/aarch64-apple-darwin/$(RegorusFFIArtifactsProfile)/libregorus_ffi.dylib')" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="docs/README.md" Pack="true" PackagePath="/" />
|
||||
<None Include="../../../LICENSE" Pack="true" PackagePath="/" />
|
||||
|
||||
<!-- Copy each binary to expected location within the package -->
|
||||
<None Include="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/*.dll" Pack="true" PackagePath="runtimes/win-x64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/*.pdb" Pack="true" PackagePath="runtimes/win-x64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/*.dll" Pack="true" PackagePath="runtimes/win-x64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/*.pdb" Pack="true" PackagePath="runtimes/win-x64/native/" />
|
||||
|
||||
<None Include="$(RegorusFFIArtifactsDir)/aarch64-pc-windows-msvc/release/*.dll" Pack="true" PackagePath="runtimes/win-arm64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/aarch64-pc-windows-msvc/release/*.pdb" Pack="true" PackagePath="runtimes/win-arm64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/aarch64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/*.dll" Pack="true" PackagePath="runtimes/win-arm64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/aarch64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/*.pdb" Pack="true" PackagePath="runtimes/win-arm64/native/" />
|
||||
|
||||
<None Include="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/lib*.so" Pack="true" PackagePath="runtimes/linux-x64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/$(RegorusFFIArtifactsProfile)/lib*.so" Pack="true" PackagePath="runtimes/linux-x64/native/" />
|
||||
|
||||
<None Include="$(RegorusFFIArtifactsDir)/aarch64-apple-darwin/release/lib*.dylib" Pack="true" PackagePath="runtimes/osx-arm64/native/" />
|
||||
<None Include="$(RegorusFFIArtifactsDir)/aarch64-apple-darwin/$(RegorusFFIArtifactsProfile)/lib*.dylib" Pack="true" PackagePath="runtimes/osx-arm64/native/" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
73
bindings/csharp/Regorus/ResultHelpers.cs
Normal file
73
bindings/csharp/Regorus/ResultHelpers.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Regorus.Internal
|
||||
{
|
||||
internal static unsafe class ResultHelpers
|
||||
{
|
||||
internal static string? GetStringResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
RegorusDataType.Integer => result.int_value.ToString(),
|
||||
RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool GetBoolResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == RegorusDataType.Boolean && result.bool_value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
internal static long GetIntResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == RegorusDataType.Integer ? result.int_value : 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
227
bindings/csharp/Regorus/Rvm.cs
Normal file
227
bindings/csharp/Regorus/Rvm.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Execution mode for the RVM runtime.
|
||||
/// </summary>
|
||||
public enum ExecutionMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Run to completion without yielding.
|
||||
/// </summary>
|
||||
RunToCompletion = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Suspendable execution mode.
|
||||
/// </summary>
|
||||
Suspendable = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for the Regorus RVM runtime.
|
||||
/// </summary>
|
||||
public unsafe sealed class Rvm : SafeHandleWrapper
|
||||
{
|
||||
public Rvm()
|
||||
: base(RegorusRvmHandle.Create(), nameof(Rvm))
|
||||
{
|
||||
}
|
||||
|
||||
private Rvm(RegorusRvmHandle handle)
|
||||
: base(handle, nameof(Rvm))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an RVM instance backed by a compiled policy (for default rule evaluation).
|
||||
/// </summary>
|
||||
public static Rvm CreateWithPolicy(CompiledPolicy policy)
|
||||
{
|
||||
if (policy is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policy));
|
||||
}
|
||||
|
||||
return policy.UseHandleForInterop(policyPtr =>
|
||||
{
|
||||
var result = API.regorus_rvm_new_with_policy((RegorusCompiledPolicy*)policyPtr);
|
||||
return GetRvmResult(result);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a program into the VM.
|
||||
/// </summary>
|
||||
public void LoadProgram(Program program)
|
||||
{
|
||||
if (program is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(program));
|
||||
}
|
||||
|
||||
program.UseHandleForInterop(programPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_load_program((RegorusRvm*)vmPtr, (RegorusProgram*)programPtr));
|
||||
return 0;
|
||||
});
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the data document for the VM.
|
||||
/// </summary>
|
||||
public void SetDataJson(string dataJson)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_data((RegorusRvm*)vmPtr, (byte*)dataPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the input document for the VM.
|
||||
/// </summary>
|
||||
public void SetInputJson(string inputJson)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_input((RegorusRvm*)vmPtr, (byte*)inputPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
/// </summary>
|
||||
public void SetExecutionMode(byte mode)
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_execution_mode((RegorusRvm*)vmPtr, mode));
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the execution mode.
|
||||
/// </summary>
|
||||
public void SetExecutionMode(ExecutionMode mode)
|
||||
{
|
||||
SetExecutionMode((byte)mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the program and return the JSON result.
|
||||
/// </summary>
|
||||
public string? Execute()
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute((RegorusRvm*)vmPtr));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a named entry point.
|
||||
/// </summary>
|
||||
public string? ExecuteEntryPoint(string entryPoint)
|
||||
{
|
||||
return Utf8Marshaller.WithUtf8(entryPoint, entryPtr =>
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_name((RegorusRvm*)vmPtr, (byte*)entryPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute an entry point by index.
|
||||
/// </summary>
|
||||
public string? ExecuteEntryPoint(ulong index)
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_index((RegorusRvm*)vmPtr, (UIntPtr)index));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume execution with an optional value.
|
||||
/// </summary>
|
||||
public string? Resume(string? resumeValueJson)
|
||||
{
|
||||
if (resumeValueJson is null)
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_resume((RegorusRvm*)vmPtr, null, has_value: false));
|
||||
});
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(resumeValueJson, valuePtr =>
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_resume((RegorusRvm*)vmPtr, (byte*)valuePtr, has_value: true));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current execution state.
|
||||
/// </summary>
|
||||
public string? GetExecutionState()
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_get_execution_state((RegorusRvm*)vmPtr));
|
||||
});
|
||||
}
|
||||
|
||||
private static Rvm GetRvmResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new Exception("Expected RVM pointer but got different data type");
|
||||
}
|
||||
|
||||
var handle = RegorusRvmHandle.FromPointer((IntPtr)result.pointer_value);
|
||||
return new Rvm(handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(RegorusResult result)
|
||||
{
|
||||
return ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
272
bindings/csharp/Regorus/SafeHandleWrapper.cs
Normal file
272
bindings/csharp/Regorus/SafeHandleWrapper.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for native handle wrappers that coordinates handle usage and disposal.
|
||||
///
|
||||
/// Behavior summary:
|
||||
/// - UseHandle: blocks Dispose while running; throws ObjectDisposedException if disposal has started or the handle is invalid.
|
||||
/// - Dispose: marks disposing and blocks new calls; waits briefly for in-flight calls to finish, then defers native release to the last exiting call if needed.
|
||||
/// - Handles are never exposed directly; derived classes can only work through UseHandle helpers.
|
||||
///
|
||||
/// Concurrency model:
|
||||
/// - _state tracks lifecycle transitions (Active -> DisposeRequested -> Released).
|
||||
/// - HandleGate tracks in-flight operations and enforces the "no new calls after Dispose" rule.
|
||||
/// - SafeHandle is pinned per call via DangerousAddRef to prevent use-after-free while native work runs.
|
||||
/// - If Dispose times out, the last in-flight caller performs the release to avoid leaks.
|
||||
/// </summary>
|
||||
public abstract class SafeHandleWrapper : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan DefaultDisposeTimeout = TimeSpan.FromMilliseconds(50);
|
||||
private const int StateActive = 0;
|
||||
private const int StateDisposeRequested = 1;
|
||||
private const int StateReleased = 2;
|
||||
private readonly HandleGate _gate;
|
||||
private readonly string _ownerName;
|
||||
private int _state;
|
||||
private SafeHandle? _handle;
|
||||
|
||||
protected SafeHandleWrapper(SafeHandle handle, string ownerName)
|
||||
{
|
||||
// Cache ownership info and initialize the gate before any use to avoid racing disposal.
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
_ownerName = ownerName ?? throw new ArgumentNullException(nameof(ownerName));
|
||||
_gate = new HandleGate(ownerName);
|
||||
// Default to a very short wait when in-flight calls exist; release is deferred to the last caller if needed.
|
||||
}
|
||||
|
||||
protected void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
// Reuse the generic path to keep add/ref/release in one place.
|
||||
UseHandle<object?>(ptr =>
|
||||
{
|
||||
action(ptr);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
// Fast reject if dispose was requested.
|
||||
if (System.Threading.Volatile.Read(ref _state) != StateActive)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
// Enter gate so Dispose waits for in-flight native calls.
|
||||
_gate.Enter();
|
||||
bool addedRef = false;
|
||||
SafeHandle? handle = null;
|
||||
try
|
||||
{
|
||||
// Race: Dispose could begin after Enter; GetHandleForUse validates the handle again.
|
||||
handle = GetHandleForUse();
|
||||
// DangerousAddRef pins the SafeHandle so Dispose cannot close it mid-call.
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
// Validate pointer after AddRef in case handle became invalid between checks.
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Always release the DangerousAddRef to avoid leaking the native handle.
|
||||
if (addedRef)
|
||||
{
|
||||
handle?.DangerousRelease();
|
||||
}
|
||||
|
||||
// Leave gate so Dispose can proceed when the last caller exits.
|
||||
var idle = _gate.Exit();
|
||||
// Race: Dispose may have timed out while we were in-flight.
|
||||
// The last exiting caller performs the native release to avoid leaks.
|
||||
if (idle && System.Threading.Volatile.Read(ref _state) == StateDisposeRequested)
|
||||
{
|
||||
TryReleaseHandle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
// Explicit alias for interop-specific call sites.
|
||||
return UseHandle(func);
|
||||
}
|
||||
|
||||
internal void UseHandleForInterop(Action<IntPtr> action)
|
||||
{
|
||||
// Explicit alias for interop-specific call sites.
|
||||
UseHandle(action);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
// Fast check for dispose state so callers fail deterministically.
|
||||
if (System.Threading.Volatile.Read(ref _state) != StateActive)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
// Validate the underlying SafeHandle is still usable; avoids races with release.
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
}
|
||||
|
||||
private SafeHandle GetHandleForUse()
|
||||
{
|
||||
// Centralized gate for derived classes to grab the handle safely.
|
||||
// This is a second line of defense in case disposal began after the initial state check.
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Only the first caller runs disposal; others become no-ops.
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _state, StateDisposeRequested, StateActive) == StateActive)
|
||||
{
|
||||
// Block new calls and wait briefly if there are in-flight operations.
|
||||
var completed = _gate.TryBeginDispose(DefaultDisposeTimeout, out var hadActive);
|
||||
if (completed)
|
||||
{
|
||||
// Either no active calls or they drained within the short timeout.
|
||||
TryReleaseHandle();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Defer release to the last in-flight caller to avoid leaks without blocking indefinitely.
|
||||
// Race: if the last in-flight caller already exited, there will be no Exit() to trigger release.
|
||||
// Re-check active state and release immediately in that case.
|
||||
if (!hadActive || _gate.IsIdle)
|
||||
{
|
||||
TryReleaseHandle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void TryReleaseHandle()
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _state, StateReleased, StateDisposeRequested) != StateDisposeRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Once released, no caller should be able to observe a valid handle.
|
||||
// SafeHandle.Dispose closes the native resource; null to prevent reuse after dispose.
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
// Release the wait handle resources after disposal completes.
|
||||
_gate.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks in-flight operations and coordinates disposal.
|
||||
/// </summary>
|
||||
private sealed class HandleGate : IDisposable
|
||||
{
|
||||
private readonly string _ownerName;
|
||||
private readonly System.Threading.ManualResetEventSlim _idle = new(initialState: true);
|
||||
private int _active;
|
||||
private int _disposing;
|
||||
|
||||
internal HandleGate(string ownerName)
|
||||
{
|
||||
_ownerName = ownerName;
|
||||
}
|
||||
|
||||
internal void Enter()
|
||||
{
|
||||
// If disposal already started, reject new work immediately.
|
||||
if (System.Threading.Volatile.Read(ref _disposing) != 0)
|
||||
{
|
||||
ThrowDisposed();
|
||||
}
|
||||
|
||||
// Track active callers; first one resets idle event.
|
||||
var active = System.Threading.Interlocked.Increment(ref _active);
|
||||
if (active == 1)
|
||||
{
|
||||
_idle.Reset();
|
||||
}
|
||||
|
||||
// Re-check disposing to handle races where Dispose began after increment.
|
||||
if (System.Threading.Volatile.Read(ref _disposing) != 0)
|
||||
{
|
||||
Exit();
|
||||
ThrowDisposed();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool Exit()
|
||||
{
|
||||
// Last caller signals idle so Dispose can continue.
|
||||
if (System.Threading.Interlocked.Decrement(ref _active) == 0)
|
||||
{
|
||||
_idle.Set();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool IsIdle => System.Threading.Volatile.Read(ref _active) == 0;
|
||||
|
||||
internal bool TryBeginDispose(TimeSpan timeout, out bool hadActive)
|
||||
{
|
||||
// Set disposing flag once; subsequent calls treat as already disposing.
|
||||
if (System.Threading.Interlocked.Exchange(ref _disposing, 1) != 0)
|
||||
{
|
||||
hadActive = System.Threading.Volatile.Read(ref _active) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
hadActive = System.Threading.Volatile.Read(ref _active) != 0;
|
||||
if (!hadActive)
|
||||
{
|
||||
// No in-flight callers; disposal can proceed without waiting.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wait for active callers to drain; optional timeout avoids blocking forever.
|
||||
if (timeout == System.Threading.Timeout.InfiniteTimeSpan)
|
||||
{
|
||||
_idle.Wait();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Race note: callers may finish between the timeout decision and Wait call; Wait handles that safely.
|
||||
return _idle.Wait(timeout);
|
||||
}
|
||||
|
||||
private void ThrowDisposed()
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_idle.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace Regorus
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
@@ -76,7 +76,7 @@ namespace Regorus
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
@@ -87,4 +87,148 @@ namespace Regorus
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegorusProgramHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusProgramHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusProgramHandle Create()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var raw = Internal.API.regorus_program_new();
|
||||
if (raw is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create Regorus program.");
|
||||
}
|
||||
|
||||
var handle = new RegorusProgramHandle();
|
||||
handle.SetHandle((IntPtr)raw);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static RegorusProgramHandle FromPointer(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||
}
|
||||
|
||||
var handle = new RegorusProgramHandle();
|
||||
handle.SetHandle(pointer);
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_program_drop((Internal.RegorusProgram*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegorusRvmHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusRvmHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusRvmHandle Create()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var raw = Internal.API.regorus_rvm_new();
|
||||
if (raw is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create Regorus RVM.");
|
||||
}
|
||||
|
||||
var handle = new RegorusRvmHandle();
|
||||
handle.SetHandle((IntPtr)raw);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static RegorusRvmHandle FromPointer(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||
}
|
||||
|
||||
var handle = new RegorusRvmHandle();
|
||||
handle.SetHandle(pointer);
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_rvm_drop((Internal.RegorusRvm*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegorusAliasRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusAliasRegistryHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusAliasRegistryHandle Create()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var raw = Internal.API.regorus_alias_registry_new();
|
||||
if (raw is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create Regorus alias registry.");
|
||||
}
|
||||
|
||||
var handle = new RegorusAliasRegistryHandle();
|
||||
handle.SetHandle((IntPtr)raw);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static RegorusAliasRegistryHandle FromPointer(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||
}
|
||||
|
||||
var handle = new RegorusAliasRegistryHandle();
|
||||
handle.SetHandle(pointer);
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_alias_registry_drop((Internal.RegorusAliasRegistry*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
@@ -28,7 +29,7 @@ namespace Regorus
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -47,7 +48,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -62,7 +63,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_len();
|
||||
return GetIntResult(result);
|
||||
return ResultHelpers.GetIntResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_is_empty();
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +88,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static string ListResourceNames()
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
|
||||
return ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all registered resource schema names as managed strings.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> GetResourceNames()
|
||||
{
|
||||
var json = ListResourceNames();
|
||||
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,7 +113,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -114,7 +124,7 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static void ClearResources()
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_resource_schema_clear());
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_clear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -131,7 +141,7 @@ namespace Regorus
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -150,7 +160,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -165,7 +175,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_len();
|
||||
return GetIntResult(result);
|
||||
return ResultHelpers.GetIntResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +189,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_is_empty();
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +200,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static string ListEffectNames()
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
|
||||
return ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all registered effect schema names as managed strings.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> GetEffectNames()
|
||||
{
|
||||
var json = ListEffectNames();
|
||||
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -206,7 +225,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -217,81 +236,7 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static void ClearEffects()
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_effect_schema_clear());
|
||||
}
|
||||
|
||||
private static string? StringFromUTF8(IntPtr ptr)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
int len = 0;
|
||||
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
|
||||
byte[] buffer = new byte[len];
|
||||
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
|
||||
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
Internal.RegorusDataType.Integer => result.int_value.ToString(),
|
||||
Internal.RegorusDataType.None => null,
|
||||
_ => StringFromUTF8((IntPtr)result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetBoolResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static long GetIntResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_clear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
bindings/csharp/Regorus/StatusExtensions.cs
Normal file
24
bindings/csharp/Regorus/StatusExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Regorus.Internal
|
||||
{
|
||||
internal static class StatusExtensions
|
||||
{
|
||||
internal static Exception CreateException(this RegorusStatus status, string? message)
|
||||
{
|
||||
var details = string.IsNullOrWhiteSpace(message) ? "Regorus call failed." : message;
|
||||
|
||||
return status switch
|
||||
{
|
||||
RegorusStatus.Panic => new InvalidOperationException($"Regorus engine panicked: {details}"),
|
||||
RegorusStatus.Poisoned => new InvalidOperationException($"Regorus engine is poisoned: {details}"),
|
||||
_ => new InvalidOperationException(details),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
@@ -27,7 +28,7 @@ namespace Regorus
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -45,7 +46,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -57,7 +58,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static string ListNames()
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
|
||||
return ResultHelpers.GetStringResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of all registered target names as managed strings.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> GetNames()
|
||||
{
|
||||
var json = ListNames();
|
||||
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,7 +83,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -84,7 +94,7 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static void Clear()
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_target_registry_clear());
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_target_registry_clear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,10 +107,9 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_len();
|
||||
return GetIntResult(result);
|
||||
return ResultHelpers.GetIntResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the target registry is empty.
|
||||
/// </summary>
|
||||
@@ -111,81 +120,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_is_empty();
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? StringFromUTF8(IntPtr ptr)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
int len = 0;
|
||||
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
|
||||
byte[] buffer = new byte[len];
|
||||
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
|
||||
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
Internal.RegorusDataType.Integer => result.int_value.ToString(),
|
||||
Internal.RegorusDataType.None => null,
|
||||
_ => StringFromUTF8((IntPtr)result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetBoolResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static long GetIntResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = StringFromUTF8((IntPtr)result.error_message);
|
||||
throw new Exception(message ?? "Unknown error occurred");
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace Regorus.Internal
|
||||
/// </summary>
|
||||
internal static class Utf8Marshaller
|
||||
{
|
||||
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
|
||||
// up to 512 bytes to cover common short strings while keeping the stack usage well
|
||||
// below typical per-frame limits; larger payloads fall back to pooled buffers.
|
||||
private const int StackAllocThreshold = 512;
|
||||
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
|
||||
// up to 512 bytes to cover common short strings while keeping the stack usage well
|
||||
// below typical per-frame limits; larger payloads fall back to pooled buffers.
|
||||
private const int StackAllocThreshold = 512;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a pooled and pinned UTF-8 buffer suitable for scenarios where
|
||||
@@ -150,5 +150,48 @@ namespace Regorus.Internal
|
||||
{
|
||||
return new PinnedUtf8(value);
|
||||
}
|
||||
|
||||
internal static unsafe string? FromUtf8(byte* pointer)
|
||||
{
|
||||
if (pointer is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
return Marshal.PtrToStringUTF8((IntPtr)pointer);
|
||||
#else
|
||||
var intPtr = (IntPtr)pointer;
|
||||
var length = 0;
|
||||
while (Marshal.ReadByte(intPtr, length) != 0)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
Marshal.Copy(intPtr, buffer, 0, length);
|
||||
return Encoding.UTF8.GetString(buffer, 0, length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static string? FromUtf8(IntPtr pointer)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return FromUtf8((byte*)pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TargetExampleApp;
|
||||
@@ -50,6 +51,69 @@ import rego.v1
|
||||
parameters.requiredTLSVersion = ""TLS1_2""
|
||||
parameters.allowedPorts = [""22"", ""3389""]";
|
||||
|
||||
private const string EXECUTION_TIMER_POLICY = @"
|
||||
package limits.timer
|
||||
import rego.v1
|
||||
|
||||
triplet_count := count([1 |
|
||||
x := data.values[_]
|
||||
y := data.values[_]
|
||||
z := data.values[_]
|
||||
])
|
||||
";
|
||||
|
||||
private const string EXECUTION_TIMER_QUERY = "data.limits.timer.triplet_count";
|
||||
private const int EXECUTION_TIMER_VALUE_COUNT = 40;
|
||||
|
||||
private const string RVM_POLICY = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
some role in data.roles[input.user]
|
||||
role == "admin"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string RVM_DATA = """
|
||||
{
|
||||
"roles": {
|
||||
"alice": ["admin", "reader"]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string RVM_INPUT = """
|
||||
{
|
||||
"user": "alice"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HOST_AWAIT_POLICY = """
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HOST_AWAIT_INPUT = """
|
||||
{
|
||||
"account": {
|
||||
"id": "acct-1",
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
// Test data constants
|
||||
private const string COMPLIANT_STORAGE_ACCOUNT = @"{
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
@@ -152,10 +216,22 @@ parameters.allowedPorts = [""22"", ""3389""]";
|
||||
|
||||
var nonCompliantResult = compiledPolicy.EvalWithInput(NON_COMPLIANT_STORAGE_ACCOUNT);
|
||||
Console.WriteLine($"Result: {nonCompliantResult}");
|
||||
|
||||
|
||||
// 4. Demonstrate thread-safe concurrent evaluation
|
||||
Console.WriteLine("\n4. Testing concurrent evaluation from multiple threads:");
|
||||
DemonstrateConcurrentEvaluation(compiledPolicy);
|
||||
|
||||
Console.WriteLine("\n5. Execution timer configuration:");
|
||||
DemonstrateExecutionTimer();
|
||||
|
||||
Console.WriteLine("\n6. RVM program execution:");
|
||||
DemonstrateRvmUsage();
|
||||
|
||||
Console.WriteLine("\n7. RVM program compilation from engine:");
|
||||
DemonstrateRvmCompileFromEngine();
|
||||
|
||||
Console.WriteLine("\n8. RVM host await (suspend/resume):");
|
||||
DemonstrateRvmHostAwait();
|
||||
}
|
||||
|
||||
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
|
||||
@@ -170,42 +246,44 @@ parameters.allowedPorts = [""22"", ""3389""]";
|
||||
};
|
||||
|
||||
Console.WriteLine($"Starting {testInputs.Length} concurrent evaluations...");
|
||||
|
||||
var tasks = testInputs.Select(input =>
|
||||
Task.Run(() => {
|
||||
|
||||
var tasks = testInputs.Select(input =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
var (threadName, json) = input;
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
|
||||
// Multiple evaluations per thread to stress test
|
||||
var results = new List<string>();
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
var result = compiledPolicy.EvalWithInput(json);
|
||||
var result = compiledPolicy.EvalWithInput(json)
|
||||
?? throw new System.InvalidOperationException("Expected EvalWithInput to return a JSON value.");
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
|
||||
stopwatch.Stop();
|
||||
var microseconds = stopwatch.ElapsedTicks * 1000000 / System.Diagnostics.Stopwatch.Frequency;
|
||||
|
||||
|
||||
// Verify all results are identical (thread safety)
|
||||
var firstResult = results[0];
|
||||
var allIdentical = results.All(r => r == firstResult);
|
||||
|
||||
|
||||
Console.WriteLine($"✓ {threadName}: {results.Count} evaluations in {microseconds}μs, " +
|
||||
$"Results consistent: {allIdentical}");
|
||||
|
||||
|
||||
return (threadName, results.Count, microseconds, allIdentical);
|
||||
})
|
||||
).ToArray();
|
||||
|
||||
// Wait for all threads to complete
|
||||
var results = Task.WhenAll(tasks).Result;
|
||||
|
||||
|
||||
Console.WriteLine("\nConcurrency test results:");
|
||||
var totalEvaluations = results.Sum(r => r.Item2);
|
||||
var maxTime = results.Max(r => r.Item3);
|
||||
var allConsistent = results.All(r => r.allIdentical);
|
||||
|
||||
|
||||
Console.WriteLine($"✓ Total evaluations: {totalEvaluations}");
|
||||
Console.WriteLine($"✓ Max thread time: {maxTime}μs");
|
||||
Console.WriteLine($"✓ All threads consistent: {allConsistent}");
|
||||
@@ -216,28 +294,28 @@ parameters.allowedPorts = [""22"", ""3389""]";
|
||||
static void DemonstratePolicyInfo(Regorus.CompiledPolicy compiledPolicy)
|
||||
{
|
||||
Console.WriteLine("Getting policy metadata using GetPolicyInfo()...");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var policyInfo = compiledPolicy.GetPolicyInfo();
|
||||
|
||||
|
||||
Console.WriteLine($"✓ Policy Information Retrieved:");
|
||||
Console.WriteLine($" Target Name: {policyInfo.TargetName ?? "None"}");
|
||||
Console.WriteLine($" Effect Rule: {policyInfo.EffectRule ?? "None"}");
|
||||
Console.WriteLine($" Entrypoint Rule: {policyInfo.EntrypointRule}");
|
||||
|
||||
|
||||
Console.WriteLine($" Module IDs ({policyInfo.ModuleIds.Count}):");
|
||||
foreach (var moduleId in policyInfo.ModuleIds)
|
||||
{
|
||||
Console.WriteLine($" - {moduleId}");
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine($" Applicable Resource Types ({policyInfo.ApplicableResourceTypes.Count}):");
|
||||
foreach (var resourceType in policyInfo.ApplicableResourceTypes)
|
||||
{
|
||||
Console.WriteLine($" - {resourceType}");
|
||||
}
|
||||
|
||||
|
||||
if (policyInfo.Parameters != null && policyInfo.Parameters.Count > 0)
|
||||
{
|
||||
Console.WriteLine($" Policy Parameters:");
|
||||
@@ -257,7 +335,7 @@ parameters.allowedPorts = [""22"", ""3389""]";
|
||||
Console.WriteLine($" Description: {param.Description}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (parameterSet.Modifiers.Count > 0)
|
||||
{
|
||||
Console.WriteLine($" Modifiers ({parameterSet.Modifiers.Count}):");
|
||||
@@ -272,11 +350,11 @@ parameters.allowedPorts = [""22"", ""3389""]";
|
||||
{
|
||||
Console.WriteLine(" No parameter information available");
|
||||
}
|
||||
|
||||
|
||||
// Demonstrate JSON serialization of policy info
|
||||
Console.WriteLine("\n✓ Policy Info as JSON:");
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
@@ -288,4 +366,130 @@ parameters.allowedPorts = [""22"", ""3389""]";
|
||||
Console.WriteLine($"✗ Failed to get policy info: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
static void DemonstrateExecutionTimer()
|
||||
{
|
||||
var dataJson = JsonSerializer.Serialize(new
|
||||
{
|
||||
values = Enumerable.Range(0, EXECUTION_TIMER_VALUE_COUNT).ToArray()
|
||||
});
|
||||
|
||||
var fallback = new Regorus.ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
|
||||
var relaxed = new Regorus.ExecutionTimerConfig(TimeSpan.FromMilliseconds(1000), checkInterval: 1);
|
||||
|
||||
Console.WriteLine($" Configuring fallback timer (limit={fallback.Limit.TotalMilliseconds:F0} ms, interval={fallback.CheckInterval})...");
|
||||
|
||||
Regorus.Engine.SetFallbackExecutionTimerConfig(fallback);
|
||||
try
|
||||
{
|
||||
using var engine = new Regorus.Engine();
|
||||
engine.AddPolicy("limits_timer.rego", EXECUTION_TIMER_POLICY);
|
||||
engine.AddDataJson(dataJson);
|
||||
|
||||
Console.WriteLine(" Evaluating under fallback limit (expected failure)...");
|
||||
try
|
||||
{
|
||||
engine.EvalRule(EXECUTION_TIMER_QUERY);
|
||||
Console.WriteLine(" ⚠ Evaluation unexpectedly succeeded under fallback limit.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($" ✓ Fallback enforced: {ex.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine($" Applying per-engine override ({relaxed.Limit.TotalMilliseconds:F0} ms) and retrying...");
|
||||
engine.SetExecutionTimerConfig(relaxed);
|
||||
var result = engine.EvalRule(EXECUTION_TIMER_QUERY);
|
||||
Console.WriteLine($" ✓ Override succeeded; triplet_count = {result}");
|
||||
|
||||
Console.WriteLine(" Clearing engine override to restore fallback...");
|
||||
engine.ClearExecutionTimerConfig();
|
||||
try
|
||||
{
|
||||
engine.EvalRule(EXECUTION_TIMER_QUERY);
|
||||
Console.WriteLine(" ⚠ Evaluation unexpectedly succeeded after clearing override.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($" ✓ Fallback restored: {ex.Message}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Regorus.Engine.ClearFallbackExecutionTimerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
static void DemonstrateRvmUsage()
|
||||
{
|
||||
var modules = new List<Regorus.PolicyModule>
|
||||
{
|
||||
new Regorus.PolicyModule("demo.rego", RVM_POLICY)
|
||||
};
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Regorus.Program.CompileFromModules(RVM_DATA, modules, entryPoints);
|
||||
var binary = program.SerializeBinary();
|
||||
using var rehydrated = Regorus.Program.DeserializeBinary(binary, out var isPartial);
|
||||
if (isPartial)
|
||||
{
|
||||
throw new InvalidOperationException("RVM program deserialization returned a partial program.");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Serialized program size: {binary.Length} bytes");
|
||||
|
||||
var listing = rehydrated.GenerateListing();
|
||||
|
||||
Console.WriteLine("RVM listing:");
|
||||
Console.WriteLine(listing);
|
||||
|
||||
using var vm = new Regorus.Rvm();
|
||||
vm.LoadProgram(rehydrated);
|
||||
vm.SetDataJson(RVM_DATA);
|
||||
vm.SetInputJson(RVM_INPUT);
|
||||
|
||||
var result = vm.Execute();
|
||||
Console.WriteLine($"RVM result: {result}");
|
||||
}
|
||||
|
||||
static void DemonstrateRvmCompileFromEngine()
|
||||
{
|
||||
using var engine = new Regorus.Engine();
|
||||
engine.AddPolicy("demo.rego", RVM_POLICY);
|
||||
engine.AddDataJson(RVM_DATA);
|
||||
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
using var program = Regorus.Program.CompileFromEngine(engine, entryPoints);
|
||||
|
||||
using var vm = new Regorus.Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetDataJson(RVM_DATA);
|
||||
vm.SetInputJson(RVM_INPUT);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("data.demo.allow");
|
||||
Console.WriteLine($"RVM result from engine-compiled program: {result}");
|
||||
}
|
||||
|
||||
static void DemonstrateRvmHostAwait()
|
||||
{
|
||||
var modules = new List<Regorus.PolicyModule>
|
||||
{
|
||||
new Regorus.PolicyModule("host_await.rego", HOST_AWAIT_POLICY)
|
||||
};
|
||||
var entryPoints = new[] { "data.demo.allow" };
|
||||
|
||||
using var program = Regorus.Program.CompileFromModules("{}", modules, entryPoints);
|
||||
using var vm = new Regorus.Rvm();
|
||||
vm.SetExecutionMode(1);
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(HOST_AWAIT_INPUT);
|
||||
|
||||
var initial = vm.Execute();
|
||||
var state = vm.GetExecutionState();
|
||||
Console.WriteLine($"HostAwait initial result: {initial}");
|
||||
Console.WriteLine($"Execution state: {state}");
|
||||
|
||||
var resumed = vm.Resume("{\"tier\":\"gold\"}");
|
||||
Console.WriteLine($"HostAwait resumed result: {resumed}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,15 @@
|
||||
<PropertyGroup>
|
||||
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
|
||||
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
|
||||
<UseLocalRegorus Condition="'$(UseLocalRegorus)' == ''">false</UseLocalRegorus>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
|
||||
<ItemGroup Condition="'$(UseLocalRegorus)' == 'true'">
|
||||
<ProjectReference Include="../Regorus/Regorus.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(UseLocalRegorus)' != 'true'">
|
||||
<PackageReference Include="Regorus" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -18,8 +18,13 @@ var w = new Stopwatch();
|
||||
|
||||
w.Restart();
|
||||
|
||||
// Configure the global pattern caches.
|
||||
Regorus.Engine.SetCacheConfig(new Regorus.CacheConfig(regex: 256, glob: 128));
|
||||
|
||||
var engine = new Regorus.Engine();
|
||||
engine.SetRegoV0(true);
|
||||
// Raise the default col limit to 2000
|
||||
engine.SetPolicyLengthConfig(new Regorus.PolicyLengthConfig(maxCol: 2000, maxFileBytes: 1048576, maxLines: 20000));
|
||||
|
||||
w.Stop();
|
||||
var newEngineTicks = w.ElapsedTicks;
|
||||
@@ -42,7 +47,8 @@ w.Restart();
|
||||
|
||||
// Set input and eval rule.
|
||||
engine.SetInputFromJsonFile("../../../tests/aci/input.json");
|
||||
var value = engine.EvalRule("data.framework.mount_overlay");
|
||||
var value = engine.EvalRule("data.framework.mount_overlay")
|
||||
?? throw new System.InvalidOperationException("Expected EvalRule to return a JSON value.");
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
var valueDoc = System.Text.Json.JsonDocument.Parse(value);
|
||||
|
||||
@@ -10,7 +10,17 @@
|
||||
<LangVersion>10.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="regorus" Version="0.8.0"/>
|
||||
<PropertyGroup>
|
||||
<!-- Allow CI to append the version suffix for locally built packages -->
|
||||
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
|
||||
<UseLocalRegorus Condition="'$(UseLocalRegorus)' == ''">false</UseLocalRegorus>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(UseLocalRegorus)' == 'true'">
|
||||
<ProjectReference Include="../Regorus/Regorus.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(UseLocalRegorus)' != 'true'">
|
||||
<PackageReference Include="Regorus" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.NoTargets": "3.7.56"
|
||||
},
|
||||
"sdk": {
|
||||
"allowPrerelease": false,
|
||||
"version": "8.0.412",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.NoTargets": "3.7.134"
|
||||
},
|
||||
"sdk": {
|
||||
"allowPrerelease": false,
|
||||
"version": "8.0.412",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
701
bindings/ffi/Cargo.lock
generated
701
bindings/ffi/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,9 @@
|
||||
|
||||
[package]
|
||||
name = "regorus-ffi"
|
||||
version = "0.5.0"
|
||||
version = "0.9.1"
|
||||
edition = "2021"
|
||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
@@ -24,13 +25,29 @@ lto = true
|
||||
codegen-units = 1
|
||||
|
||||
[features]
|
||||
default = ["ast", "azure_policy", "std", "coverage", "regorus/arc", "regorus/full-opa", "contention_checks"]
|
||||
default = [
|
||||
"ast",
|
||||
"azure_policy",
|
||||
"std",
|
||||
"coverage",
|
||||
"allocator-memory-limits",
|
||||
"rvm",
|
||||
"rbac",
|
||||
"regorus/arc",
|
||||
"regorus/full-opa",
|
||||
"cache",
|
||||
"contention_checks",
|
||||
]
|
||||
ast = ["regorus/ast"]
|
||||
azure_policy = ["regorus/azure_policy"]
|
||||
std = ["regorus/std"]
|
||||
coverage = ["regorus/coverage"]
|
||||
allocator-memory-limits = ["regorus/allocator-memory-limits"]
|
||||
contention_checks = ["parking_lot"]
|
||||
rvm = ["regorus/rvm"]
|
||||
rbac = ["regorus/azure-rbac"]
|
||||
cache = ["regorus/cache"]
|
||||
custom_allocator = []
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.28.0"
|
||||
cbindgen = "0.29.2"
|
||||
|
||||
479
bindings/ffi/src/alias_registry.rs
Normal file
479
bindings/ffi/src/alias_registry.rs
Normal file
@@ -0,0 +1,479 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! FFI bindings for `AliasRegistry` – Azure Policy alias catalog management.
|
||||
|
||||
#![cfg(feature = "azure_policy")]
|
||||
|
||||
use crate::common::{from_c_str, to_ref, RegorusResult, RegorusStatus};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use anyhow::Result;
|
||||
use core::ffi::c_char;
|
||||
use core::ptr;
|
||||
|
||||
use regorus::languages::azure_policy::aliases::AliasRegistry;
|
||||
|
||||
/// Opaque wrapper for `AliasRegistry`.
|
||||
pub struct RegorusAliasRegistry {
|
||||
registry: AliasRegistry,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Create a new, empty `AliasRegistry`.
|
||||
///
|
||||
/// The caller must eventually call `regorus_alias_registry_drop` to free the handle.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_new() -> *mut RegorusAliasRegistry {
|
||||
let wrapper = RegorusAliasRegistry {
|
||||
registry: AliasRegistry::new(),
|
||||
};
|
||||
Box::into_raw(Box::new(wrapper))
|
||||
}
|
||||
|
||||
/// Drop a `RegorusAliasRegistry`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
|
||||
if let Ok(r) = to_ref(registry) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(ptr::from_mut(r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Load control-plane alias data (array of `ProviderAliases`) into the registry.
|
||||
///
|
||||
/// `json` must be a valid null-terminated UTF-8 string containing the JSON
|
||||
/// array returned by `Get-AzPolicyAlias` or the static
|
||||
/// `ResourceTypesAndAliases.json` file.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_load_json(
|
||||
registry: *mut RegorusAliasRegistry,
|
||||
json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<()> {
|
||||
let json_str = from_c_str(json)?;
|
||||
to_ref(registry)?.registry.load_from_json(&json_str)?;
|
||||
Ok(())
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to load alias catalog: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a data-plane policy manifest into the registry.
|
||||
///
|
||||
/// `json` must be a valid null-terminated UTF-8 string containing a single
|
||||
/// `DataPolicyManifest` JSON object.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_load_manifest(
|
||||
registry: *mut RegorusAliasRegistry,
|
||||
json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<()> {
|
||||
let json_str = from_c_str(json)?;
|
||||
to_ref(registry)?
|
||||
.registry
|
||||
.load_data_policy_manifest_json(&json_str)?;
|
||||
Ok(())
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to load data-plane manifest: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Return the number of resource types loaded in the alias registry.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_len(registry: *mut RegorusAliasRegistry) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<i64> {
|
||||
let len = to_ref(registry)?.registry.len();
|
||||
Ok(len as i64)
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(n) => RegorusResult::ok_int(n),
|
||||
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Normalize / Denormalize
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Normalize an ARM resource JSON and wrap it into the standard input envelope.
|
||||
///
|
||||
/// Returns a JSON string:
|
||||
/// `{ "resource": <normalized>, "context": <context>, "parameters": <params> }`.
|
||||
///
|
||||
/// * `resource_json` – raw ARM resource JSON
|
||||
/// * `api_version` – API version string (e.g. `"2023-01-01"`), or null to use
|
||||
/// the default alias paths
|
||||
/// * `context_json` – JSON object for additional context (pass `"{}"` if none)
|
||||
/// * `parameters_json` – JSON object of policy parameter values (pass `"{}"` if none)
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
||||
registry: *mut RegorusAliasRegistry,
|
||||
resource_json: *const c_char,
|
||||
api_version: *const c_char,
|
||||
context_json: *const c_char,
|
||||
parameters_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let resource_str = from_c_str(resource_json)?;
|
||||
let api_ver = if api_version.is_null() {
|
||||
None
|
||||
} else {
|
||||
let s = from_c_str(api_version)?;
|
||||
if s.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(s)
|
||||
}
|
||||
};
|
||||
let context_str = from_c_str(context_json)?;
|
||||
let params_str = from_c_str(parameters_json)?;
|
||||
|
||||
let resource = regorus::Value::from_json_str(&resource_str)?;
|
||||
let context = regorus::Value::from_json_str(&context_str)?;
|
||||
let params = regorus::Value::from_json_str(¶ms_str)?;
|
||||
|
||||
let wrapped = to_ref(registry)?.registry.normalize_and_wrap(
|
||||
&resource,
|
||||
api_ver.as_deref(),
|
||||
Some(context),
|
||||
Some(params),
|
||||
);
|
||||
wrapped.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(s) => RegorusResult::ok_string(s),
|
||||
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||
///
|
||||
/// * `normalized_json` – the normalized resource JSON
|
||||
/// * `api_version` – API version string, or null to use the default alias paths
|
||||
///
|
||||
/// Returns the denormalized ARM JSON string.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_denormalize(
|
||||
registry: *mut RegorusAliasRegistry,
|
||||
normalized_json: *const c_char,
|
||||
api_version: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let normalized_str = from_c_str(normalized_json)?;
|
||||
let api_ver = if api_version.is_null() {
|
||||
None
|
||||
} else {
|
||||
let s = from_c_str(api_version)?;
|
||||
if s.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(s)
|
||||
}
|
||||
};
|
||||
|
||||
let normalized = regorus::Value::from_json_str(&normalized_str)?;
|
||||
|
||||
let result = to_ref(registry)?
|
||||
.registry
|
||||
.denormalize(&normalized, api_ver.as_deref());
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(s) => RegorusResult::ok_string(s),
|
||||
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::common::regorus_result_drop;
|
||||
use core::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
|
||||
/// Helper: create a C string from a Rust &str.
|
||||
fn c(s: &str) -> CString {
|
||||
CString::new(s).expect("CString::new failed")
|
||||
}
|
||||
|
||||
/// Helper: assert a RegorusResult has Ok status and extract string output.
|
||||
fn assert_ok_string(r: &RegorusResult) -> String {
|
||||
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
||||
assert!(!r.output.is_null(), "expected non-null output");
|
||||
let s = unsafe { CStr::from_ptr(r.output) }
|
||||
.to_str()
|
||||
.expect("invalid UTF-8 in output")
|
||||
.to_string();
|
||||
s
|
||||
}
|
||||
|
||||
/// Helper: assert a RegorusResult has Ok status with integer output.
|
||||
fn assert_ok_int(r: &RegorusResult) -> i64 {
|
||||
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
||||
r.int_value
|
||||
}
|
||||
|
||||
const ALIASES: &str = r#"[{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [{
|
||||
"resourceType": "storageAccounts",
|
||||
"aliases": [{
|
||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||
"paths": []
|
||||
}]
|
||||
}]
|
||||
}]"#;
|
||||
|
||||
const MANIFEST: &str = r#"{
|
||||
"dataNamespace": "Microsoft.KeyVault.Data",
|
||||
"aliases": [],
|
||||
"resourceTypeAliases": [{
|
||||
"resourceType": "vaults/certificates",
|
||||
"aliases": [{
|
||||
"name": "Microsoft.KeyVault.Data/vaults/certificates/keySize",
|
||||
"paths": [{ "path": "keySize", "apiVersions": ["7.0"] }]
|
||||
}]
|
||||
}]
|
||||
}"#;
|
||||
|
||||
#[test]
|
||||
fn lifecycle_new_and_drop() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
assert!(!reg.is_null());
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_json_and_check_len() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let json = c(ALIASES);
|
||||
|
||||
let r = regorus_alias_registry_load_json(reg, json.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let r = regorus_alias_registry_len(reg);
|
||||
assert_eq!(assert_ok_int(&r), 1);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_manifest_and_check_len() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let json = c(MANIFEST);
|
||||
|
||||
let r = regorus_alias_registry_load_manifest(reg, json.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let r = regorus_alias_registry_len(reg);
|
||||
assert_eq!(assert_ok_int(&r), 1);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_invalid_json_returns_error() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let bad = c("not valid json");
|
||||
|
||||
let r = regorus_alias_registry_load_json(reg, bad.as_ptr());
|
||||
assert_ne!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_and_wrap_round_trip() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let aliases = c(ALIASES);
|
||||
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let resource = c(r#"{
|
||||
"name": "acct1",
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"properties": { "supportsHttpsTrafficOnly": true }
|
||||
}"#);
|
||||
let api = c("2023-01-01");
|
||||
let ctx = c(r#"{"resourceGroup": {"name": "rg1"}}"#);
|
||||
let params = c(r#"{"env": "prod"}"#);
|
||||
|
||||
// Normalize
|
||||
let r = regorus_alias_registry_normalize_and_wrap(
|
||||
reg,
|
||||
resource.as_ptr(),
|
||||
api.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
params.as_ptr(),
|
||||
);
|
||||
let envelope_json = assert_ok_string(&r);
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Parse and verify structure
|
||||
let envelope: serde_json::Value =
|
||||
serde_json::from_str(&envelope_json).expect("invalid JSON output");
|
||||
assert!(
|
||||
envelope.get("resource").is_some(),
|
||||
"envelope missing 'resource'"
|
||||
);
|
||||
assert!(
|
||||
envelope.get("parameters").is_some(),
|
||||
"envelope missing 'parameters'"
|
||||
);
|
||||
assert!(
|
||||
envelope.get("context").is_some(),
|
||||
"envelope missing 'context'"
|
||||
);
|
||||
|
||||
// The normalized resource should have lowercased alias fields
|
||||
let res = &envelope["resource"];
|
||||
assert_eq!(res["supportshttpstrafficonly"], true);
|
||||
assert_eq!(res["name"], "acct1");
|
||||
|
||||
// Context and parameters should be passed through
|
||||
assert_eq!(envelope["context"]["resourceGroup"]["name"], "rg1");
|
||||
assert_eq!(envelope["parameters"]["env"], "prod");
|
||||
|
||||
// Denormalize the resource portion
|
||||
let resource_json = serde_json::to_string(&res).expect("serialize resource");
|
||||
let norm_cstr = c(&resource_json);
|
||||
|
||||
let r = regorus_alias_registry_denormalize(reg, norm_cstr.as_ptr(), api.as_ptr());
|
||||
let denorm_json = assert_ok_string(&r);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let denorm: serde_json::Value =
|
||||
serde_json::from_str(&denorm_json).expect("invalid denorm JSON");
|
||||
// Should be back under properties with restored casing
|
||||
assert_eq!(
|
||||
denorm["properties"]["supportsHttpsTrafficOnly"], true,
|
||||
"expected restored casing under properties"
|
||||
);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denormalize_invalid_json_returns_error() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let aliases = c(ALIASES);
|
||||
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let bad = c("not json");
|
||||
let api = c("2023-01-01");
|
||||
let r = regorus_alias_registry_denormalize(reg, bad.as_ptr(), api.as_ptr());
|
||||
assert_ne!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_data_plane_manifest() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let manifest = c(MANIFEST);
|
||||
let r = regorus_alias_registry_load_manifest(reg, manifest.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let resource = c(r#"{
|
||||
"type": "Microsoft.KeyVault.Data/vaults/certificates",
|
||||
"keySize": 2048
|
||||
}"#);
|
||||
let api = c("7.0");
|
||||
let ctx = c("{}");
|
||||
let params = c("{}");
|
||||
|
||||
let r = regorus_alias_registry_normalize_and_wrap(
|
||||
reg,
|
||||
resource.as_ptr(),
|
||||
api.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
params.as_ptr(),
|
||||
);
|
||||
let envelope_json = assert_ok_string(&r);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let envelope: serde_json::Value =
|
||||
serde_json::from_str(&envelope_json).expect("invalid JSON output");
|
||||
assert_eq!(envelope["resource"]["keysize"], 2048);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_registry_normalize() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let resource = c(r#"{"name": "test", "type": "Unknown/type", "properties": {"foo": 1}}"#);
|
||||
let api = c("");
|
||||
let ctx = c("{}");
|
||||
let params = c("{}");
|
||||
|
||||
let r = regorus_alias_registry_normalize_and_wrap(
|
||||
reg,
|
||||
resource.as_ptr(),
|
||||
api.as_ptr(),
|
||||
ctx.as_ptr(),
|
||||
params.as_ptr(),
|
||||
);
|
||||
let json = assert_ok_string(&r);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let envelope: serde_json::Value = serde_json::from_str(&json).expect("invalid JSON");
|
||||
// Without aliases, properties should still be flattened
|
||||
assert_eq!(envelope["resource"]["foo"], 1);
|
||||
assert_eq!(envelope["resource"]["name"], "test");
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::ffi::CString;
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use core::ffi::{c_char, c_longlong, c_void, CStr};
|
||||
use core::ptr;
|
||||
use core::{mem, ptr};
|
||||
|
||||
/// Status of a call on `RegorusEngine`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub enum RegorusStatus {
|
||||
/// The operation was successful.
|
||||
@@ -34,6 +37,12 @@ pub enum RegorusStatus {
|
||||
|
||||
/// Invalid policy content.
|
||||
InvalidPolicy,
|
||||
|
||||
/// The engine panicked and cannot be reused until reset.
|
||||
Panic,
|
||||
|
||||
/// The engine remains poisoned because a previous panic was detected.
|
||||
Poisoned,
|
||||
}
|
||||
|
||||
/// Type of data contained in RegorusResult
|
||||
@@ -84,6 +93,19 @@ pub struct RegorusResult {
|
||||
pub(crate) error_message: *mut c_char,
|
||||
}
|
||||
|
||||
/// Byte buffer returned from FFI for binary payloads.
|
||||
///
|
||||
/// Must be freed using `regorus_buffer_drop`.
|
||||
#[repr(C)]
|
||||
pub struct RegorusBuffer {
|
||||
/// Pointer to byte buffer data.
|
||||
pub data: *mut u8,
|
||||
/// Number of bytes stored in `data`.
|
||||
pub len: usize,
|
||||
/// Capacity of the allocation backing `data`.
|
||||
pub capacity: usize,
|
||||
}
|
||||
|
||||
impl RegorusResult {
|
||||
/// Create a successful result with no data.
|
||||
pub(crate) fn ok_void() -> Self {
|
||||
@@ -179,6 +201,18 @@ impl RegorusResult {
|
||||
}
|
||||
}
|
||||
|
||||
impl RegorusBuffer {
|
||||
pub(crate) fn from_vec(mut data: Vec<u8>) -> *mut RegorusBuffer {
|
||||
let buffer = RegorusBuffer {
|
||||
data: data.as_mut_ptr(),
|
||||
len: data.len(),
|
||||
capacity: data.capacity(),
|
||||
};
|
||||
mem::forget(data);
|
||||
Box::into_raw(Box::new(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_c_str(s: String) -> *mut c_char {
|
||||
match CString::new(s) {
|
||||
Ok(cs) => cs.into_raw(),
|
||||
@@ -216,6 +250,21 @@ pub(crate) fn to_regorus_string_result(r: Result<String>) -> RegorusResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusBuffer`.
|
||||
///
|
||||
/// `data` is not valid after drop.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_buffer_drop(buffer: *mut RegorusBuffer) {
|
||||
if let Ok(buffer) = to_ref(buffer) {
|
||||
unsafe {
|
||||
if !buffer.data.is_null() {
|
||||
let _ = Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity);
|
||||
}
|
||||
let _ = Box::from_raw(ptr::from_mut(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusResult`.
|
||||
///
|
||||
/// `output` and `error_message` strings are not valid after drop.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
@@ -43,55 +44,54 @@ pub extern "C" fn regorus_compile_policy_with_entrypoint(
|
||||
modules_len: usize,
|
||||
entry_point_rule: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let entry_rule = match from_c_str(entry_point_rule) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidEntrypoint,
|
||||
format!("Invalid entry point rule string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let entry_rule = match from_c_str(entry_point_rule) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidEntrypoint,
|
||||
format!("Invalid entry point rule string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse data JSON
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Convert C modules array to Rust Vec
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
|
||||
// Call the convenience function
|
||||
match compile_policy_with_entrypoint(data, &policy_modules, entry_rule.into()) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match compile_policy_with_entrypoint(data, &policy_modules, entry_rule.into()) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compiles a target-aware policy from data and modules.
|
||||
@@ -122,45 +122,44 @@ pub extern "C" fn regorus_compile_policy_for_target(
|
||||
modules: *const RegorusPolicyModule,
|
||||
modules_len: usize,
|
||||
) -> RegorusResult {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let data_str = match from_c_str(data_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid data JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse data JSON
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let data = match Value::from_json_str(&data_str) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse data JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Convert C modules array to Rust Vec
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
let policy_modules = match convert_c_modules_to_rust(modules, modules_len) {
|
||||
Ok(modules) => modules,
|
||||
Err(status) => return RegorusResult::err(status),
|
||||
};
|
||||
|
||||
// Call the convenience function
|
||||
match compile_policy_for_target(data, &policy_modules) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match compile_policy_for_target(data, &policy_modules) {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Target-aware policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Target-aware policy compilation failed: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to convert C module array to Rust Vec<PolicyModule>.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::*;
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use anyhow::Result;
|
||||
@@ -35,20 +36,23 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
input: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
|
||||
let result = to_ref(compiled_policy)?
|
||||
.compiled_policy
|
||||
.eval_with_input(input_value)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
|
||||
let result = to_ref(compiled_policy)?
|
||||
.compiled_policy
|
||||
.eval_with_input(input_value)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure the execution timer for evaluations of this compiled policy.
|
||||
/// Get information about the compiled policy including metadata about modules,
|
||||
/// target configuration, and resource types.
|
||||
///
|
||||
@@ -59,14 +63,16 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
|
||||
pub extern "C" fn regorus_compiled_policy_get_policy_info(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let info = to_ref(compiled_policy)?.compiled_policy.get_policy_info()?;
|
||||
serde_json::to_string(&info)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
|
||||
}();
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let info = to_ref(compiled_policy)?.compiled_policy.get_policy_info()?;
|
||||
serde_json::to_string(&info)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#![cfg(feature = "azure_policy")]
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use regorus::{registry::schemas, Schema};
|
||||
|
||||
use std::os::raw::c_char;
|
||||
@@ -29,45 +30,45 @@ pub extern "C" fn regorus_effect_schema_register(
|
||||
name: *const c_char,
|
||||
schema_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid effect schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid effect schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse schema from JSON
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse effect schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse effect schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Register the schema
|
||||
match schemas::effect::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register effect schema: {e}"),
|
||||
),
|
||||
}
|
||||
match schemas::effect::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register effect schema: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if an effect schema with the given name exists.
|
||||
@@ -83,18 +84,20 @@ pub extern "C" fn regorus_effect_schema_register(
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_contains(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let contains = schemas::effect::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
let contains = schemas::effect::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the number of registered effect schemas.
|
||||
@@ -104,8 +107,10 @@ pub extern "C" fn regorus_effect_schema_contains(name: *const c_char) -> Regorus
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_len() -> RegorusResult {
|
||||
let count = schemas::effect::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
with_unwind_guard(|| {
|
||||
let count = schemas::effect::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the effect schema registry is empty.
|
||||
@@ -115,8 +120,10 @@ pub extern "C" fn regorus_effect_schema_len() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_is_empty() -> RegorusResult {
|
||||
let is_empty = schemas::effect::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
with_unwind_guard(|| {
|
||||
let is_empty = schemas::effect::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
/// List all registered effect schema names as a JSON array.
|
||||
@@ -126,14 +133,16 @@ pub extern "C" fn regorus_effect_schema_is_empty() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_list_names() -> RegorusResult {
|
||||
let names = schemas::effect::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize effect schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let names = schemas::effect::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize effect schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove an effect schema by name.
|
||||
@@ -149,18 +158,20 @@ pub extern "C" fn regorus_effect_schema_list_names() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_remove(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid effect schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let removed = schemas::effect::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
let removed = schemas::effect::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear all effect schemas from the registry.
|
||||
@@ -170,6 +181,8 @@ pub extern "C" fn regorus_effect_schema_remove(name: *const c_char) -> RegorusRe
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_effect_schema_clear() -> RegorusResult {
|
||||
schemas::effect::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
with_unwind_guard(|| {
|
||||
schemas::effect::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,13 +5,23 @@ use crate::common::{
|
||||
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, RegorusResult, RegorusStatus,
|
||||
};
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::limits::RegorusExecutionTimerConfig;
|
||||
use crate::limits::RegorusPolicyLengthConfig;
|
||||
use crate::lock::{new_handle, read, try_read, try_write, Handle, ReadGuard, WriteGuard};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
#[cfg(feature = "rvm")]
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::ffi::{c_char, c_void};
|
||||
use core::ptr;
|
||||
#[cfg(feature = "rvm")]
|
||||
use regorus::languages::rego::compiler::Compiler;
|
||||
#[cfg(feature = "rvm")]
|
||||
use regorus::rvm::program::Program;
|
||||
|
||||
/// Wrapper for `regorus::Engine`.
|
||||
pub struct RegorusEngine {
|
||||
@@ -67,6 +77,101 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod panic_tests {
|
||||
use super::{
|
||||
regorus_engine_drop, regorus_engine_eval_query, regorus_engine_get_policies,
|
||||
regorus_engine_new,
|
||||
};
|
||||
use crate::common::{regorus_result_drop, RegorusStatus};
|
||||
use crate::panic_guard::{is_poisoned, reset_poison};
|
||||
use alloc::boxed::Box;
|
||||
use regorus::Value;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
#[test]
|
||||
fn catches_extension_panics_and_marks_poison() {
|
||||
reset_poison();
|
||||
|
||||
let engine_ptr = regorus_engine_new();
|
||||
assert!(!engine_ptr.is_null(), "engine allocation must succeed");
|
||||
assert!(!is_poisoned(), "guard must start unpoisoned");
|
||||
|
||||
unsafe {
|
||||
let engine = &mut *engine_ptr;
|
||||
{
|
||||
let mut guard = engine
|
||||
.try_write()
|
||||
.expect("exclusive access to configure engine");
|
||||
guard
|
||||
.add_extension(
|
||||
"panic_extension".to_string(),
|
||||
0,
|
||||
Box::new(|_| -> anyhow::Result<Value> { panic!("ffi extension panic") }),
|
||||
)
|
||||
.expect("extension registration must succeed");
|
||||
guard
|
||||
.add_policy(
|
||||
"panic.rego".to_string(),
|
||||
"package panic\n\ndefault allow = false\n\nallow if {\n panic_extension()\n}"
|
||||
.to_string(),
|
||||
)
|
||||
.expect("policy registration must succeed");
|
||||
}
|
||||
}
|
||||
|
||||
let query = CString::new("data.panic.allow").expect("valid query string");
|
||||
let panic_result = regorus_engine_eval_query(engine_ptr, query.as_ptr());
|
||||
assert!(matches!(panic_result.status, RegorusStatus::Panic));
|
||||
unsafe {
|
||||
assert!(
|
||||
!panic_result.error_message.is_null(),
|
||||
"panic details must be present"
|
||||
);
|
||||
let message = CStr::from_ptr(panic_result.error_message)
|
||||
.to_str()
|
||||
.expect("error message utf8");
|
||||
assert!(
|
||||
message.contains("ffi extension panic"),
|
||||
"panic payload must bubble across guard"
|
||||
);
|
||||
}
|
||||
regorus_result_drop(panic_result);
|
||||
assert!(is_poisoned(), "engine must be marked poisoned after panic");
|
||||
|
||||
let poisoned_result = regorus_engine_get_policies(engine_ptr);
|
||||
assert!(matches!(poisoned_result.status, RegorusStatus::Poisoned));
|
||||
unsafe {
|
||||
assert!(
|
||||
!poisoned_result.error_message.is_null(),
|
||||
"poison message must be present"
|
||||
);
|
||||
let message = CStr::from_ptr(poisoned_result.error_message)
|
||||
.to_str()
|
||||
.expect("poison message utf8");
|
||||
assert!(
|
||||
message.contains("regorus is poisoned"),
|
||||
"poison message must inform callers"
|
||||
);
|
||||
}
|
||||
regorus_result_drop(poisoned_result);
|
||||
|
||||
regorus_engine_drop(engine_ptr);
|
||||
reset_poison();
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "std")]
|
||||
pub extern "C" fn regorus_engine_test_trigger_panic() -> RegorusResult {
|
||||
with_unwind_guard(|| panic!("regorus ffi test panic"))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_test_reset_poison() {
|
||||
crate::panic_guard::reset_poison();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Construct a new Engine
|
||||
///
|
||||
@@ -116,11 +221,13 @@ pub extern "C" fn regorus_engine_add_policy(
|
||||
path: *const c_char,
|
||||
rego: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -129,11 +236,13 @@ pub extern "C" fn regorus_engine_add_policy_from_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy_from_file(from_c_str(path)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy_from_file(from_c_str(path)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Add policy data.
|
||||
@@ -145,11 +254,13 @@ pub extern "C" fn regorus_engine_add_data_json(
|
||||
engine: *mut RegorusEngine,
|
||||
data: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get list of loaded Rego packages as JSON.
|
||||
@@ -157,11 +268,13 @@ pub extern "C" fn regorus_engine_add_data_json(
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get list of policies as JSON.
|
||||
@@ -169,11 +282,13 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_policies_as_json()
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_policies_as_json()
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -182,11 +297,13 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear policy data.
|
||||
@@ -194,12 +311,14 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_data();
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_data();
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set input.
|
||||
@@ -211,12 +330,14 @@ pub extern "C" fn regorus_engine_set_input_json(
|
||||
engine: *mut RegorusEngine,
|
||||
input: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -225,12 +346,14 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate query.
|
||||
@@ -242,16 +365,18 @@ pub extern "C" fn regorus_engine_eval_query(
|
||||
engine: *mut RegorusEngine,
|
||||
query: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let results = guard.eval_query(from_c_str(query)?, false)?;
|
||||
Ok(serde_json::to_string_pretty(&results)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let results = guard.eval_query(from_c_str(query)?, false)?;
|
||||
Ok(serde_json::to_string_pretty(&results)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate specified rule.
|
||||
@@ -263,15 +388,17 @@ pub extern "C" fn regorus_engine_eval_rule(
|
||||
engine: *mut RegorusEngine,
|
||||
rule: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable/disable coverage.
|
||||
@@ -284,12 +411,14 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
||||
engine: *mut RegorusEngine,
|
||||
enable: bool,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_enable_coverage(enable);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_enable_coverage(enable);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get coverage report.
|
||||
@@ -298,15 +427,17 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable/disable strict builtin errors.
|
||||
@@ -317,15 +448,81 @@ pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine)
|
||||
pub extern "C" fn regorus_engine_set_strict_builtin_errors(
|
||||
engine: *mut RegorusEngine,
|
||||
strict: bool,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Configure the execution timer for a specific engine instance.
|
||||
pub extern "C" fn regorus_engine_set_execution_timer_config(
|
||||
engine: *mut RegorusEngine,
|
||||
config: *const RegorusExecutionTimerConfig,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let config = unsafe {
|
||||
config
|
||||
.as_ref()
|
||||
.copied()
|
||||
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
|
||||
};
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_execution_timer_config(config.to_execution_timer_config()?);
|
||||
Ok(())
|
||||
}())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Clear the engine-specific execution timer configuration.
|
||||
pub extern "C" fn regorus_engine_clear_execution_timer_config(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
guard.clear_execution_timer_config();
|
||||
Ok(())
|
||||
}())
|
||||
}
|
||||
|
||||
/// Set the policy length limits used when loading policies.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_set_policy_length_config(
|
||||
engine: *mut RegorusEngine,
|
||||
config: RegorusPolicyLengthConfig,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_policy_length_config(config.to_policy_length_config()?);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear the policy length configuration, reverting to defaults.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_clear_policy_length_config(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_policy_length_config();
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get pretty printed coverage report.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
|
||||
@@ -334,15 +531,17 @@ pub extern "C" fn regorus_engine_set_strict_builtin_errors(
|
||||
pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_coverage_report()?.to_string_pretty()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_coverage_report()?.to_string_pretty()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear coverage data.
|
||||
@@ -351,12 +550,14 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_coverage_data();
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_coverage_data();
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether to gather output of print statements.
|
||||
@@ -368,12 +569,14 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
engine: *mut RegorusEngine,
|
||||
enable: bool,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_gather_prints(enable);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_gather_prints(enable);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Take all the gathered print statements.
|
||||
@@ -381,15 +584,17 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get AST of policies.
|
||||
@@ -398,15 +603,17 @@ pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> Rego
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "ast")]
|
||||
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_ast_as_json()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_ast_as_json()
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the package names defined in each policy added to the engine.
|
||||
@@ -417,15 +624,18 @@ pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) ->
|
||||
pub extern "C" fn regorus_engine_get_policy_package_names(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_package_names()?).map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_package_names()?)
|
||||
.map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the parameters defined in each policy added to the engine.
|
||||
@@ -436,15 +646,18 @@ pub extern "C" fn regorus_engine_get_policy_package_names(
|
||||
pub extern "C" fn regorus_engine_get_policy_parameters(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_parameters()?).map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_parameters()?)
|
||||
.map_err(anyhow::Error::msg)
|
||||
}();
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable/disable rego v1.
|
||||
@@ -455,16 +668,18 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
||||
engine: *mut RegorusEngine,
|
||||
enable: bool,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_rego_v0(enable);
|
||||
Ok(())
|
||||
}();
|
||||
match output {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_rego_v0(enable);
|
||||
Ok(())
|
||||
}();
|
||||
match output {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile a target-aware policy from the current engine state.
|
||||
@@ -476,37 +691,39 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_engine_compile_for_target(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let engine = match to_ref(engine) {
|
||||
Ok(engine) => engine,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Failed to get engine reference: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let engine = match to_ref(engine) {
|
||||
Ok(engine) => engine,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Failed to get engine reference: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let mut guard = match engine.try_write() {
|
||||
Ok(guard) => guard,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to lock engine: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let mut guard = match engine.try_write() {
|
||||
Ok(guard) => guard,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to lock engine: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
match guard.compile_for_target() {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match guard.compile_for_target() {
|
||||
Ok(compiled_policy) => {
|
||||
let wrapped_policy = RegorusCompiledPolicy { compiled_policy };
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile for target: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile for target: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile a policy with a specific entry point rule.
|
||||
@@ -520,23 +737,87 @@ pub extern "C" fn regorus_engine_compile_with_entrypoint(
|
||||
engine: *mut RegorusEngine,
|
||||
rule: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let result = || -> Result<RegorusCompiledPolicy> {
|
||||
let rule_str = from_c_str(rule)?;
|
||||
let rule_rc: regorus::Rc<str> = rule_str.into();
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
Ok(RegorusCompiledPolicy { compiled_policy })
|
||||
}();
|
||||
with_unwind_guard(|| {
|
||||
let result = || -> Result<RegorusCompiledPolicy> {
|
||||
let rule_str = from_c_str(rule)?;
|
||||
let rule_rc: regorus::Rc<str> = rule_str.into();
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
Ok(RegorusCompiledPolicy { compiled_policy })
|
||||
}();
|
||||
|
||||
match result {
|
||||
Ok(wrapped_policy) => {
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
match result {
|
||||
Ok(wrapped_policy) => {
|
||||
let boxed_policy = Box::new(wrapped_policy);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed_policy) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile with entrypoint: {e}"),
|
||||
),
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile with entrypoint: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile an RVM program from the engine state with entry points.
|
||||
///
|
||||
/// * `entry_points` - Array of entry point rule paths
|
||||
/// * `entry_points_len` - Number of entry points
|
||||
#[cfg(feature = "rvm")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_compile_program_with_entrypoints(
|
||||
engine: *mut RegorusEngine,
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let result = || -> Result<Arc<Program>> {
|
||||
if entry_points_len == 0 {
|
||||
return Err(anyhow!("entry_points must contain at least one entry"));
|
||||
}
|
||||
|
||||
if entry_points.is_null() && entry_points_len > 0 {
|
||||
return Err(anyhow!("null entry_points pointer"));
|
||||
}
|
||||
|
||||
let mut entry_points_vec = Vec::with_capacity(entry_points_len);
|
||||
for i in 0..entry_points_len {
|
||||
unsafe {
|
||||
let entry_ptr = entry_points.add(i);
|
||||
if entry_ptr.is_null() {
|
||||
return Err(anyhow!("null entry point at index {i}"));
|
||||
}
|
||||
let entry = from_c_str(*entry_ptr)?;
|
||||
entry_points_vec.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let rule = entry_points_ref
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
|
||||
let rule_rc: regorus::Rc<str> = (*rule).into();
|
||||
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
|
||||
let program = Compiler::compile_from_policy(&compiled_policy, &entry_points_ref)?;
|
||||
Ok(program)
|
||||
}();
|
||||
|
||||
match result {
|
||||
Ok(program) => {
|
||||
let wrapped = crate::rvm::RegorusProgram { program };
|
||||
let boxed = Box::new(wrapped);
|
||||
RegorusResult::ok_pointer(Box::into_raw(boxed) as *mut c_void)
|
||||
}
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile RVM program: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod alias_registry;
|
||||
mod allocator;
|
||||
mod common;
|
||||
mod compile;
|
||||
mod compiled_policy;
|
||||
mod effect_registry;
|
||||
mod engine;
|
||||
mod limits;
|
||||
mod lock;
|
||||
mod panic_guard;
|
||||
#[cfg(feature = "rbac")]
|
||||
mod rbac;
|
||||
#[cfg(feature = "rvm")]
|
||||
pub(crate) mod rvm;
|
||||
mod schema_registry;
|
||||
mod target_registry;
|
||||
|
||||
276
bindings/ffi/src/limits.rs
Normal file
276
bindings/ffi/src/limits.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::{to_regorus_result, RegorusResult, RegorusStatus};
|
||||
use alloc::format;
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::num::{NonZeroU32, NonZeroUsize};
|
||||
use core::time::Duration;
|
||||
use regorus::utils::limits::{self, ExecutionTimerConfig};
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
fn some_or_none(flag: bool, value: u64) -> Option<u64> {
|
||||
if flag {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
fn optional_u64_to_result(value: Option<u64>) -> RegorusResult {
|
||||
match value {
|
||||
Some(bytes) => {
|
||||
if bytes > i64::MAX as u64 {
|
||||
RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!(
|
||||
"value {bytes} exceeds i64::MAX ({max}) bridge limit",
|
||||
max = i64::MAX
|
||||
),
|
||||
)
|
||||
} else {
|
||||
let mut result = RegorusResult::ok_int(bytes as i64);
|
||||
result.bool_value = true;
|
||||
result
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut result = RegorusResult::ok_void();
|
||||
result.bool_value = false;
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_set_global_memory_limit(limit: u64, has_limit: bool) -> RegorusResult {
|
||||
::regorus::set_global_memory_limit(some_or_none(has_limit, limit));
|
||||
RegorusResult::ok_void()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_set_global_memory_limit(_limit: u64, _has_limit: bool) -> RegorusResult {
|
||||
feature_disabled("regorus_set_global_memory_limit")
|
||||
}
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_get_global_memory_limit() -> RegorusResult {
|
||||
optional_u64_to_result(::regorus::global_memory_limit())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_get_global_memory_limit() -> RegorusResult {
|
||||
feature_disabled("regorus_get_global_memory_limit")
|
||||
}
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_check_global_memory_limit() -> RegorusResult {
|
||||
match ::regorus::check_global_memory_limit() {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{err}")),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_check_global_memory_limit() -> RegorusResult {
|
||||
feature_disabled("regorus_check_global_memory_limit")
|
||||
}
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_flush_thread_memory_counters() -> RegorusResult {
|
||||
::regorus::flush_thread_memory_counters();
|
||||
RegorusResult::ok_void()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_flush_thread_memory_counters() -> RegorusResult {
|
||||
feature_disabled("regorus_flush_thread_memory_counters")
|
||||
}
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_set_thread_flush_threshold_override(
|
||||
bytes: u64,
|
||||
has_threshold: bool,
|
||||
) -> RegorusResult {
|
||||
::regorus::set_thread_flush_threshold_override(some_or_none(has_threshold, bytes));
|
||||
RegorusResult::ok_void()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_set_thread_flush_threshold_override(
|
||||
_bytes: u64,
|
||||
_has_threshold: bool,
|
||||
) -> RegorusResult {
|
||||
feature_disabled("regorus_set_thread_flush_threshold_override")
|
||||
}
|
||||
|
||||
#[cfg(feature = "allocator-memory-limits")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_get_thread_memory_flush_threshold() -> RegorusResult {
|
||||
optional_u64_to_result(::regorus::thread_memory_flush_threshold())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_get_thread_memory_flush_threshold() -> RegorusResult {
|
||||
feature_disabled("regorus_get_thread_memory_flush_threshold")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
||||
fn feature_disabled(function: &str) -> RegorusResult {
|
||||
RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("{function} unavailable: regorus built without allocator-memory-limits feature"),
|
||||
)
|
||||
}
|
||||
|
||||
/// FFI representation of [`ExecutionTimerConfig`].
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RegorusExecutionTimerConfig {
|
||||
/// Wall-clock limit expressed in nanoseconds.
|
||||
pub limit_ns: u64,
|
||||
/// Number of work units between timer checks (must be non-zero).
|
||||
pub check_interval: u32,
|
||||
}
|
||||
|
||||
impl RegorusExecutionTimerConfig {
|
||||
pub fn to_execution_timer_config(self) -> Result<ExecutionTimerConfig> {
|
||||
let check_interval = NonZeroU32::new(self.check_interval)
|
||||
.ok_or_else(|| anyhow!("execution_timer.check_interval must be non-zero"))?;
|
||||
let limit = Duration::from_nanos(self.limit_ns);
|
||||
|
||||
Ok(ExecutionTimerConfig {
|
||||
limit,
|
||||
check_interval,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// FFI representation of [`regorus::PolicyLengthConfig`].
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RegorusPolicyLengthConfig {
|
||||
/// Maximum column width per line (must be non-zero).
|
||||
pub max_col: u32,
|
||||
/// Maximum policy file size in bytes (must be non-zero).
|
||||
pub max_file_bytes: usize,
|
||||
/// Maximum number of lines per policy file (must be non-zero).
|
||||
pub max_lines: usize,
|
||||
}
|
||||
|
||||
impl RegorusPolicyLengthConfig {
|
||||
pub fn to_policy_length_config(self) -> Result<regorus::PolicyLengthConfig> {
|
||||
Ok(regorus::PolicyLengthConfig {
|
||||
max_col: NonZeroU32::new(self.max_col)
|
||||
.ok_or_else(|| anyhow!("max_col must be non-zero"))?,
|
||||
max_file_bytes: NonZeroUsize::new(self.max_file_bytes)
|
||||
.ok_or_else(|| anyhow!("max_file_bytes must be non-zero"))?,
|
||||
max_lines: NonZeroUsize::new(self.max_lines)
|
||||
.ok_or_else(|| anyhow!("max_lines must be non-zero"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_set_fallback_execution_timer_config(
|
||||
config: RegorusExecutionTimerConfig,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
limits::set_fallback_execution_timer_config(Some(config.to_execution_timer_config()?));
|
||||
Ok(())
|
||||
}())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_clear_fallback_execution_timer_config() -> RegorusResult {
|
||||
limits::set_fallback_execution_timer_config(None);
|
||||
RegorusResult::ok_void()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cache configuration (global)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// FFI representation of [`regorus::cache::Config`].
|
||||
#[cfg(feature = "cache")]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RegorusCacheConfig {
|
||||
/// Maximum compiled regex patterns (default 256, 0 = disabled).
|
||||
pub regex: usize,
|
||||
/// Maximum compiled glob matchers (default 128, 0 = disabled).
|
||||
pub glob: usize,
|
||||
}
|
||||
|
||||
/// Configure the global pattern caches used by `regex.*` and `glob.*` builtins.
|
||||
#[cfg(feature = "cache")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_set_cache_config(config: RegorusCacheConfig) -> RegorusResult {
|
||||
regorus::cache::configure(regorus::cache::Config {
|
||||
regex: config.regex,
|
||||
glob: config.glob,
|
||||
});
|
||||
RegorusResult::ok_void()
|
||||
}
|
||||
|
||||
/// Clear all entries from every pattern cache.
|
||||
#[cfg(feature = "cache")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_clear_cache() -> RegorusResult {
|
||||
regorus::cache::clear();
|
||||
RegorusResult::ok_void()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
optional_u64_to_result, regorus_get_global_memory_limit, regorus_set_global_memory_limit,
|
||||
};
|
||||
use crate::common::{regorus_result_drop, RegorusDataType, RegorusStatus};
|
||||
|
||||
#[test]
|
||||
fn optional_some_returns_integer() {
|
||||
let result = optional_u64_to_result(Some(123));
|
||||
assert!(result.bool_value);
|
||||
assert!(matches!(result.data_type, RegorusDataType::Integer));
|
||||
assert_eq!(result.int_value, 123);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_none_returns_void() {
|
||||
let result = optional_u64_to_result(None);
|
||||
assert!(!result.bool_value);
|
||||
assert!(matches!(result.data_type, RegorusDataType::None));
|
||||
assert_eq!(result.int_value, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ffi_roundtrips_global_limit() {
|
||||
let limit = 456_u64;
|
||||
let result = regorus_set_global_memory_limit(limit, true);
|
||||
assert!(matches!(result.status, RegorusStatus::Ok));
|
||||
regorus_result_drop(result);
|
||||
|
||||
let result = regorus_get_global_memory_limit();
|
||||
assert!(matches!(result.status, RegorusStatus::Ok));
|
||||
assert!(result.bool_value);
|
||||
assert!(matches!(result.data_type, RegorusDataType::Integer));
|
||||
assert_eq!(result.int_value, 456);
|
||||
regorus_result_drop(result);
|
||||
|
||||
let result = regorus_set_global_memory_limit(0, false);
|
||||
regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
159
bindings/ffi/src/panic_guard.rs
Normal file
159
bindings/ffi/src/panic_guard.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Minimal helpers for catching panics inside the FFI layer.
|
||||
//!
|
||||
//! These are not yet wired into the exported functions; they will
|
||||
//! be used once the integration work is complete.
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use crate::common::{RegorusResult, RegorusStatus};
|
||||
|
||||
use alloc::string::String;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
#[cfg(feature = "std")]
|
||||
use std::{
|
||||
backtrace::Backtrace,
|
||||
cell::RefCell,
|
||||
panic::{self, AssertUnwindSafe},
|
||||
};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
thread_local! {
|
||||
// Stashes the formatted panic + backtrace for whichever call last panicked on this thread.
|
||||
static PANIC_BACKTRACE: RefCell<Option<String>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
type PanicHook = dyn Fn(&panic::PanicHookInfo<'_>) + Sync + Send + 'static;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// RAII helper that installs a per-call panic hook and restores the prior hook on drop.
|
||||
struct PanicHookGuard {
|
||||
previous: Option<Box<PanicHook>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl PanicHookGuard {
|
||||
fn install() -> Self {
|
||||
// Remember whatever hook the embedding application already registered.
|
||||
let previous = panic::take_hook();
|
||||
PANIC_BACKTRACE.with(|slot| {
|
||||
slot.replace(None);
|
||||
});
|
||||
// Install our temporary hook so we can capture a backtrace for this invocation.
|
||||
panic::set_hook(Box::new(|info| {
|
||||
let backtrace = Backtrace::force_capture();
|
||||
PANIC_BACKTRACE.with(|slot| {
|
||||
slot.replace(Some(format!(
|
||||
"panic hook observed: {}\nbacktrace:\n{:#?}",
|
||||
info, backtrace
|
||||
)));
|
||||
});
|
||||
}));
|
||||
Self {
|
||||
previous: Some(previous),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Drop for PanicHookGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(previous) = self.previous.take() {
|
||||
// Restore the original panic hook before we return control to the host.
|
||||
panic::set_hook(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static POISONED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Result of attempting to run `f` while guarding against unwinding.
|
||||
pub(crate) enum GuardResult<T> {
|
||||
/// Closure completed successfully.
|
||||
Success(T),
|
||||
/// Closure panicked; contains a best-effort string payload.
|
||||
Panic(String),
|
||||
}
|
||||
|
||||
pub(crate) fn with_unwind_guard<F>(f: F) -> RegorusResult
|
||||
where
|
||||
F: FnOnce() -> RegorusResult,
|
||||
{
|
||||
if is_poisoned() {
|
||||
return poisoned_result();
|
||||
}
|
||||
|
||||
// The closure passed across this boundary closes over raw pointers and lock guards.
|
||||
// These types are not unwind safe by default and may become poisoned if a panic occurs.
|
||||
// We therefore use AssertUnwindSafe to get the compiler to accept the closure.
|
||||
// Upon unwind, we mark regorus as poisoned and disallow further use.
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
let outcome = {
|
||||
let _hook_guard = PanicHookGuard::install();
|
||||
match panic::catch_unwind(AssertUnwindSafe(f)) {
|
||||
Ok(value) => GuardResult::Success(value),
|
||||
Err(payload) => GuardResult::Panic(panic_message_to_string(payload)),
|
||||
}
|
||||
};
|
||||
finalize(outcome)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
return finalize(GuardResult::Success(f()));
|
||||
}
|
||||
|
||||
fn finalize(outcome: GuardResult<RegorusResult>) -> RegorusResult {
|
||||
match outcome {
|
||||
GuardResult::Success(result) => result,
|
||||
GuardResult::Panic(message) => {
|
||||
trip_poison();
|
||||
RegorusResult::err_with_message(RegorusStatus::Panic, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn panic_message_to_string(payload: Box<dyn core::any::Any + Send + 'static>) -> String {
|
||||
let mut message = if let Some(s) = payload.downcast_ref::<&str>() {
|
||||
(*s).into()
|
||||
} else if let Some(s) = payload.downcast_ref::<String>() {
|
||||
s.clone()
|
||||
} else {
|
||||
String::from("regorus encountered panic")
|
||||
};
|
||||
|
||||
if let Some(backtrace) = take_panic_backtrace() {
|
||||
message.push('\n');
|
||||
message.push_str(&backtrace);
|
||||
}
|
||||
|
||||
message
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn take_panic_backtrace() -> Option<String> {
|
||||
PANIC_BACKTRACE.with(|slot| slot.borrow_mut().take())
|
||||
}
|
||||
|
||||
fn poisoned_result() -> RegorusResult {
|
||||
RegorusResult::err_with_message(
|
||||
RegorusStatus::Poisoned,
|
||||
String::from("regorus is poisoned after a previous panic"),
|
||||
)
|
||||
}
|
||||
|
||||
fn trip_poison() {
|
||||
POISONED.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
pub(crate) fn is_poisoned() -> bool {
|
||||
POISONED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn reset_poison() {
|
||||
POISONED.store(false, Ordering::Release);
|
||||
}
|
||||
59
bindings/ffi/src/rbac.rs
Normal file
59
bindings/ffi/src/rbac.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::format;
|
||||
use core::ffi::c_char;
|
||||
|
||||
use regorus::languages::azure_rbac::ast::EvaluationContext;
|
||||
use regorus::languages::azure_rbac::interpreter::ConditionInterpreter;
|
||||
|
||||
#[no_mangle]
|
||||
/// Evaluate an Azure RBAC condition expression against a JSON evaluation context.
|
||||
///
|
||||
/// * `condition`: RBAC condition string.
|
||||
/// * `context_json`: JSON representation of EvaluationContext.
|
||||
pub extern "C" fn regorus_rbac_engine_eval_condition(
|
||||
condition: *const c_char,
|
||||
context_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let condition = match from_c_str(condition) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("{err}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let context_json = match from_c_str(context_json) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("{err}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let context: EvaluationContext = match serde_json::from_str(&context_json) {
|
||||
Ok(context) => context,
|
||||
Err(err) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("invalid context json: {err}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let interpreter = ConditionInterpreter::new(&context);
|
||||
match interpreter.evaluate_str(&condition) {
|
||||
Ok(result) => RegorusResult::ok_bool(result),
|
||||
Err(err) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("condition evaluation failed: {err}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
606
bindings/ffi/src/rvm.rs
Normal file
606
bindings/ffi/src/rvm.rs
Normal file
@@ -0,0 +1,606 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::{
|
||||
from_c_str, to_ref, to_regorus_result, RegorusBuffer, RegorusResult, RegorusStatus,
|
||||
};
|
||||
use crate::compile::RegorusPolicyModule;
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::limits::RegorusExecutionTimerConfig;
|
||||
use crate::lock::{new_handle, try_read, try_write, Handle, ReadGuard, WriteGuard};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::ffi::{c_char, c_void};
|
||||
use core::ptr;
|
||||
use regorus::languages::rego::compiler::Compiler;
|
||||
use regorus::rvm::program::{
|
||||
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
|
||||
DeserializationResult, Program,
|
||||
};
|
||||
use regorus::rvm::vm::{ExecutionMode, ExecutionState, RegoVM};
|
||||
use regorus::PolicyModule;
|
||||
use regorus::Value;
|
||||
|
||||
/// Wrapper for `regorus::rvm::Program`.
|
||||
#[derive(Clone)]
|
||||
pub struct RegorusProgram {
|
||||
pub(crate) program: Arc<Program>,
|
||||
}
|
||||
|
||||
/// Wrapper for `regorus::rvm::RegoVM`.
|
||||
pub struct RegorusRvm {
|
||||
vm: Handle<RegoVM>,
|
||||
}
|
||||
|
||||
impl RegorusRvm {
|
||||
fn new(vm: RegoVM) -> Self {
|
||||
Self { vm: new_handle(vm) }
|
||||
}
|
||||
|
||||
fn contention_error() -> anyhow::Error {
|
||||
anyhow!("regorus rvm handle is already in use; create a separate VM per thread")
|
||||
}
|
||||
|
||||
fn try_write(&self) -> Result<WriteGuard<'_, RegoVM>> {
|
||||
try_write(&self.vm).ok_or_else(Self::contention_error)
|
||||
}
|
||||
|
||||
fn try_read(&self) -> Result<ReadGuard<'_, RegoVM>> {
|
||||
try_read(&self.vm).ok_or_else(Self::contention_error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusProgram`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_drop(program: *mut RegorusProgram) {
|
||||
if let Ok(program) = to_ref(program) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(ptr::from_mut(program));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop a `RegorusRvm`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_drop(vm: *mut RegorusRvm) {
|
||||
if let Ok(vm) = to_ref(vm) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(ptr::from_mut(vm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile a compiled policy into an RVM program.
|
||||
///
|
||||
/// * `compiled_policy` - Compiled policy handle
|
||||
/// * `entry_points` - Array of entry point rule paths
|
||||
/// * `entry_points_len` - Number of entry points
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_compile_from_policy(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusProgram> {
|
||||
if entry_points.is_null() && entry_points_len > 0 {
|
||||
return Err(anyhow!("null entry_points pointer"));
|
||||
}
|
||||
|
||||
let mut entry_points_vec = Vec::with_capacity(entry_points_len);
|
||||
for i in 0..entry_points_len {
|
||||
unsafe {
|
||||
let entry_ptr = entry_points.add(i);
|
||||
if entry_ptr.is_null() {
|
||||
return Err(anyhow!("null entry point at index {i}"));
|
||||
}
|
||||
let entry = from_c_str(*entry_ptr)?;
|
||||
entry_points_vec.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let compiled_policy = &to_ref(compiled_policy)?.compiled_policy;
|
||||
let program = Compiler::compile_from_policy(compiled_policy, &entry_points_ref)?;
|
||||
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(program) => RegorusResult::ok_pointer(program as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("RVM compilation failed: {err}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile an RVM program from data/modules and entry points.
|
||||
///
|
||||
/// * `data_json` - JSON string containing static data for policy evaluation
|
||||
/// * `modules` - Array of policy modules to compile
|
||||
/// * `modules_len` - Number of modules in the array
|
||||
/// * `entry_points` - Array of entry point rule paths
|
||||
/// * `entry_points_len` - Number of entry points
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_compile_from_modules(
|
||||
data_json: *const c_char,
|
||||
modules: *const RegorusPolicyModule,
|
||||
modules_len: usize,
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusProgram> {
|
||||
if entry_points_len == 0 {
|
||||
return Err(anyhow!("entry_points must contain at least one entry"));
|
||||
}
|
||||
|
||||
let data_str = from_c_str(data_json)?;
|
||||
let data = Value::from_json_str(&data_str)?;
|
||||
let policy_modules = convert_c_modules_to_rust(modules, modules_len)?;
|
||||
|
||||
let entry_points_vec = convert_c_entry_points(entry_points, entry_points_len)?;
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let entry_rule = entry_points_ref
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
|
||||
|
||||
let compiled_policy = regorus::compile_policy_with_entrypoint(
|
||||
data,
|
||||
&policy_modules,
|
||||
(*entry_rule).into(),
|
||||
)?;
|
||||
|
||||
let program = Compiler::compile_from_policy(&compiled_policy, &entry_points_ref)?;
|
||||
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(program) => RegorusResult::ok_pointer(program as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("RVM compilation failed: {err}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new, empty RVM program.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_new() -> *mut RegorusProgram {
|
||||
let program = Program::new();
|
||||
Box::into_raw(Box::new(RegorusProgram {
|
||||
program: Arc::new(program),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Serialize a program to the binary RVM format.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_serialize_binary(program: *mut RegorusProgram) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusBuffer> {
|
||||
let program = &to_ref(program)?.program;
|
||||
let bytes = program.serialize_binary().map_err(|e| anyhow!(e))?;
|
||||
Ok(RegorusBuffer::from_vec(bytes))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(buffer) => RegorusResult::ok_pointer(buffer as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{err}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Deserialize a program from the binary RVM format.
|
||||
///
|
||||
/// Returns a `RegorusProgram` handle and sets `is_partial` to true when the
|
||||
/// program requires recompilation.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_deserialize_binary(
|
||||
data: *const u8,
|
||||
len: usize,
|
||||
is_partial: *mut bool,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<(*mut RegorusProgram, bool)> {
|
||||
if data.is_null() && len > 0 {
|
||||
return Err(anyhow!("null data pointer"));
|
||||
}
|
||||
let data = unsafe { core::slice::from_raw_parts(data, len) };
|
||||
let (program, partial) =
|
||||
match Program::deserialize_binary(data).map_err(|e| anyhow!(e))? {
|
||||
DeserializationResult::Complete(program) => (program, false),
|
||||
DeserializationResult::Partial(program) => (program, true),
|
||||
};
|
||||
Ok((
|
||||
Box::into_raw(Box::new(RegorusProgram {
|
||||
program: Arc::new(program),
|
||||
})),
|
||||
partial,
|
||||
))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok((program, partial)) => {
|
||||
if !is_partial.is_null() {
|
||||
unsafe {
|
||||
*is_partial = partial;
|
||||
}
|
||||
}
|
||||
RegorusResult::ok_pointer(program as *mut c_void)
|
||||
}
|
||||
Err(err) => {
|
||||
RegorusResult::err_with_message(RegorusStatus::InvalidDataFormat, err.to_string())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a default assembly listing for the program.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_generate_listing(program: *mut RegorusProgram) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let program = &to_ref(program)?.program;
|
||||
Ok(generate_assembly_listing(
|
||||
program,
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(listing) => RegorusResult::ok_string(listing),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{err}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a tabular assembly listing for the program.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_program_generate_tabular_listing(
|
||||
program: *mut RegorusProgram,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let program = &to_ref(program)?.program;
|
||||
Ok(generate_tabular_assembly_listing(
|
||||
program,
|
||||
&AssemblyListingConfig::default(),
|
||||
))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(listing) => RegorusResult::ok_string(listing),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{err}")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a new RVM instance.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_new() -> *mut RegorusRvm {
|
||||
Box::into_raw(Box::new(RegorusRvm::new(RegoVM::new())))
|
||||
}
|
||||
|
||||
/// Construct a new RVM instance with a compiled policy for default rule evaluation.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_new_with_policy(
|
||||
compiled_policy: *mut RegorusCompiledPolicy,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusRvm> {
|
||||
let policy = to_ref(compiled_policy)?.compiled_policy.clone();
|
||||
Ok(Box::into_raw(Box::new(RegorusRvm::new(
|
||||
RegoVM::new_with_policy(policy),
|
||||
))))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(vm) => RegorusResult::ok_pointer(vm as *mut c_void),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a program into the RVM.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_load_program(
|
||||
vm: *mut RegorusRvm,
|
||||
program: *mut RegorusProgram,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let program = to_ref(program)?.program.clone();
|
||||
guard.load_program(program);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the VM data document from JSON.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_data(vm: *mut RegorusRvm, data: *const c_char) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let data_value = Value::from_json_str(&from_c_str(data)?)?;
|
||||
guard.set_data(data_value)?;
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the VM input document from JSON.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_input(
|
||||
vm: *mut RegorusRvm,
|
||||
input: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let input_value = Value::from_json_str(&from_c_str(input)?)?;
|
||||
guard.set_input(input_value);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the maximum number of instructions that can execute.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_max_instructions(
|
||||
vm: *mut RegorusRvm,
|
||||
max_instructions: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_max_instructions(max_instructions);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure strict builtin error behavior.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
|
||||
vm: *mut RegorusRvm,
|
||||
strict: bool,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure the execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let mode = match mode {
|
||||
0 => ExecutionMode::RunToCompletion,
|
||||
1 => ExecutionMode::Suspendable,
|
||||
_ => return Err(anyhow!("invalid execution mode: {mode}")),
|
||||
};
|
||||
guard.set_execution_mode(mode);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable or disable step mode when running suspendable execution.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_step_mode(vm: *mut RegorusRvm, enabled: bool) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_step_mode(enabled);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure the per-VM execution timer override.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_execution_timer_config(
|
||||
vm: *mut RegorusRvm,
|
||||
has_config: bool,
|
||||
config: RegorusExecutionTimerConfig,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
if has_config {
|
||||
guard.set_execution_timer_config(Some(config.to_execution_timer_config()?));
|
||||
} else {
|
||||
guard.set_execution_timer_config(None);
|
||||
}
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute the program's main entry point.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_execute(vm: *mut RegorusRvm) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let result = guard.execute()?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute a named entry point.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_execute_entry_point_by_name(
|
||||
vm: *mut RegorusRvm,
|
||||
entry_point: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let name = from_c_str(entry_point)?;
|
||||
let result = guard.execute_entry_point_by_name(&name)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute an entry point by index.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_execute_entry_point_by_index(
|
||||
vm: *mut RegorusRvm,
|
||||
index: usize,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let result = guard.execute_entry_point_by_index(index)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Resume execution for suspendable runs.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_resume(
|
||||
vm: *mut RegorusRvm,
|
||||
resume_value_json: *const c_char,
|
||||
has_value: bool,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let value = if has_value {
|
||||
Some(Value::from_json_str(&from_c_str(resume_value_json)?)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let result = guard.resume(value)?;
|
||||
result.to_json_str()
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current execution state of the VM.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_get_execution_state(vm: *mut RegorusRvm) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let guard = vm.try_read()?;
|
||||
let state: ExecutionState = guard.execution_state().clone();
|
||||
Ok(format!("{:?}", state))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(json) => RegorusResult::ok_string(json),
|
||||
Err(err) => RegorusResult::err_with_message(RegorusStatus::Error, err.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_c_entry_points(
|
||||
entry_points: *const *const c_char,
|
||||
entry_points_len: usize,
|
||||
) -> Result<Vec<String>> {
|
||||
if entry_points.is_null() && entry_points_len > 0 {
|
||||
return Err(anyhow!("null entry_points pointer"));
|
||||
}
|
||||
|
||||
let mut entry_points_vec = Vec::with_capacity(entry_points_len);
|
||||
for i in 0..entry_points_len {
|
||||
unsafe {
|
||||
let entry_ptr = entry_points.add(i);
|
||||
if entry_ptr.is_null() {
|
||||
return Err(anyhow!("null entry point at index {i}"));
|
||||
}
|
||||
let entry = from_c_str(*entry_ptr)?;
|
||||
entry_points_vec.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entry_points_vec)
|
||||
}
|
||||
|
||||
fn convert_c_modules_to_rust(
|
||||
modules: *const RegorusPolicyModule,
|
||||
modules_len: usize,
|
||||
) -> Result<Vec<PolicyModule>> {
|
||||
if modules.is_null() && modules_len > 0 {
|
||||
return Err(anyhow!("null modules pointer"));
|
||||
}
|
||||
|
||||
let mut policy_modules = Vec::with_capacity(modules_len);
|
||||
|
||||
for i in 0..modules_len {
|
||||
unsafe {
|
||||
let module = modules.add(i);
|
||||
if module.is_null() {
|
||||
return Err(anyhow!("null module at index {i}"));
|
||||
}
|
||||
|
||||
let module_ref = &*module;
|
||||
|
||||
let id = from_c_str(module_ref.id)
|
||||
.map_err(|e| anyhow!("invalid module id at index {i}: {e}"))?;
|
||||
let content = from_c_str(module_ref.content)
|
||||
.map_err(|e| anyhow!("invalid module content at index {i}: {e}"))?;
|
||||
|
||||
policy_modules.push(PolicyModule {
|
||||
id: id.into(),
|
||||
content: content.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(policy_modules)
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#![cfg(feature = "azure_policy")]
|
||||
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use regorus::{registry::schemas, Schema};
|
||||
|
||||
use std::os::raw::c_char;
|
||||
@@ -32,45 +33,45 @@ pub extern "C" fn regorus_resource_schema_register(
|
||||
name: *const c_char,
|
||||
schema_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema_str = match from_c_str(schema_json) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid schema JSON string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse schema from JSON
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
let schema = match Schema::from_json_str(&schema_str) {
|
||||
Ok(schema) => schema,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to parse schema JSON: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Register the schema
|
||||
match schemas::resource::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register schema: {e}"),
|
||||
),
|
||||
}
|
||||
match schemas::resource::register(schema_name, schema.into()) {
|
||||
Ok(()) => RegorusResult::ok_pointer(std::ptr::null_mut()),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to register schema: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a resource schema with the given name exists.
|
||||
@@ -86,18 +87,20 @@ pub extern "C" fn regorus_resource_schema_register(
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_contains(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let contains = schemas::resource::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
let contains = schemas::resource::contains(&schema_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the number of registered resource schemas.
|
||||
@@ -107,8 +110,10 @@ pub extern "C" fn regorus_resource_schema_contains(name: *const c_char) -> Regor
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_len() -> RegorusResult {
|
||||
let count = schemas::resource::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
with_unwind_guard(|| {
|
||||
let count = schemas::resource::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the resource schema registry is empty.
|
||||
@@ -118,8 +123,10 @@ pub extern "C" fn regorus_resource_schema_len() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_is_empty() -> RegorusResult {
|
||||
let is_empty = schemas::resource::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
with_unwind_guard(|| {
|
||||
let is_empty = schemas::resource::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
/// List all registered resource schema names as a JSON array.
|
||||
@@ -129,14 +136,16 @@ pub extern "C" fn regorus_resource_schema_is_empty() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_list_names() -> RegorusResult {
|
||||
let names = schemas::resource::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
with_unwind_guard(|| {
|
||||
let names = schemas::resource::list_names();
|
||||
match serde_json::to_string(&names) {
|
||||
Ok(json_str) => RegorusResult::ok_string(json_str),
|
||||
Err(e) => RegorusResult::err_with_message(
|
||||
RegorusStatus::Error,
|
||||
format!("Failed to serialize schema names to JSON: {e}"),
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove a resource schema by name.
|
||||
@@ -152,18 +161,20 @@ pub extern "C" fn regorus_resource_schema_list_names() -> RegorusResult {
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_remove(name: *const c_char) -> RegorusResult {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let schema_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid schema name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let removed = schemas::resource::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
let removed = schemas::resource::remove(&schema_name).is_some();
|
||||
RegorusResult::ok_bool(removed)
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear all resource schemas from the registry.
|
||||
@@ -173,6 +184,8 @@ pub extern "C" fn regorus_resource_schema_remove(name: *const c_char) -> Regorus
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_resource_schema_clear() -> RegorusResult {
|
||||
schemas::resource::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
with_unwind_guard(|| {
|
||||
schemas::resource::clear();
|
||||
RegorusResult::ok_pointer(std::ptr::null_mut())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#![cfg(feature = "azure_policy")]
|
||||
|
||||
use crate::common::*;
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use anyhow::Result;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
@@ -16,12 +17,14 @@ use std::os::raw::c_char;
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_register_target_from_json(target_json: *const c_char) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let target_str = from_c_str(target_json)?;
|
||||
let target = regorus::Target::from_json_str(&target_str)?;
|
||||
regorus::registry::targets::register(regorus::Rc::new(target))?;
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let target_str = from_c_str(target_json)?;
|
||||
let target = regorus::Target::from_json_str(&target_str)?;
|
||||
regorus::registry::targets::register(regorus::Rc::new(target))?;
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a target is registered.
|
||||
@@ -36,31 +39,35 @@ pub extern "C" fn regorus_register_target_from_json(target_json: *const c_char)
|
||||
/// The name parameter must be a valid null-terminated UTF-8 string.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_target_registry_contains(name: *const c_char) -> RegorusResult {
|
||||
let target_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid target name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
with_unwind_guard(|| {
|
||||
let target_name = match from_c_str(name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid target name string: {e}"),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let contains = regorus::registry::targets::contains(&target_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
let contains = regorus::registry::targets::contains(&target_name);
|
||||
RegorusResult::ok_bool(contains)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a list of all registered target names as JSON array.
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_list_names() -> RegorusResult {
|
||||
let names = regorus::registry::targets::list_names();
|
||||
let output = serde_json::to_string_pretty(&names).map_err(anyhow::Error::msg);
|
||||
with_unwind_guard(|| {
|
||||
let names = regorus::registry::targets::list_names();
|
||||
let output = serde_json::to_string_pretty(&names).map_err(anyhow::Error::msg);
|
||||
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
match output {
|
||||
Ok(out) => RegorusResult::ok_string(out),
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove a target from the registry by name.
|
||||
@@ -69,19 +76,23 @@ pub extern "C" fn regorus_target_registry_list_names() -> RegorusResult {
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_remove(name: *const c_char) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let name_str = from_c_str(name)?;
|
||||
regorus::registry::targets::remove(&name_str);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let name_str = from_c_str(name)?;
|
||||
regorus::registry::targets::remove(&name_str);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Clear all targets from the registry.
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_clear() -> RegorusResult {
|
||||
regorus::registry::targets::clear();
|
||||
RegorusResult::ok_void()
|
||||
with_unwind_guard(|| {
|
||||
regorus::registry::targets::clear();
|
||||
RegorusResult::ok_void()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the number of registered targets.
|
||||
@@ -91,8 +102,10 @@ pub extern "C" fn regorus_target_registry_clear() -> RegorusResult {
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_len() -> RegorusResult {
|
||||
let count = regorus::registry::targets::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
with_unwind_guard(|| {
|
||||
let count = regorus::registry::targets::len();
|
||||
RegorusResult::ok_int(count as i64)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the target registry is empty.
|
||||
@@ -102,6 +115,8 @@ pub extern "C" fn regorus_target_registry_len() -> RegorusResult {
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_target_registry_is_empty() -> RegorusResult {
|
||||
let is_empty = regorus::registry::targets::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
with_unwind_guard(|| {
|
||||
let is_empty = regorus::registry::targets::is_empty();
|
||||
RegorusResult::ok_bool(is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,7 +17,15 @@ func main() {
|
||||
engine := regorus.NewEngine()
|
||||
defer engine.Close()
|
||||
|
||||
// Configure the global pattern caches.
|
||||
if err = regorus.SetCacheConfig(regorus.CacheConfig{Regex: 256, Glob: 128}); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
engine.SetRegoV0(true)
|
||||
// Raise the default col limit to 2000
|
||||
engine.SetPolicyLengthConfig(regorus.PolicyLengthConfig{MaxCol: 2000, MaxFileBytes: 1048576, MaxLines: 20000})
|
||||
elapsed1 := time.Since(t)
|
||||
|
||||
|
||||
@@ -100,4 +108,138 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%s\n", output)
|
||||
|
||||
// RVM regular example (compile, serialize, execute)
|
||||
const regularPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
input.active == true
|
||||
}
|
||||
`
|
||||
const regularInput = `{"user":"alice","active":true}`
|
||||
|
||||
regularModules := []regorus.PolicyModule{{Id: "demo.rego", Content: regularPolicy}}
|
||||
regularEntryPoints := []string{"data.demo.allow"}
|
||||
regularProgram, err := regorus.CompileProgramFromModules("{}", regularModules, regularEntryPoints)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer regularProgram.Close()
|
||||
|
||||
listing, err := regularProgram.GenerateListing()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("RVM listing:\n%s\n", listing)
|
||||
|
||||
binary, err := regularProgram.SerializeBinary()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
regularProgram.Close()
|
||||
|
||||
rehydrated, isPartial, err := regorus.DeserializeProgram(binary)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if isPartial {
|
||||
fmt.Fprintf(os.Stderr, "error: program marked partial\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer rehydrated.Close()
|
||||
|
||||
regularVm, err := regorus.NewRvm()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer regularVm.Close()
|
||||
|
||||
if err := regularVm.LoadProgram(rehydrated); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := regularVm.SetInputJson(regularInput); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
regularResult, err := regularVm.Execute()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("RVM regular result: %s\n", regularResult)
|
||||
|
||||
// RVM HostAwait example
|
||||
const rvmPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
`
|
||||
const rvmInput = `{"account":{"id":"acct-1","active":true}}`
|
||||
|
||||
modules := []regorus.PolicyModule{{Id: "demo.rego", Content: rvmPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := regorus.CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
vm, err := regorus.NewRvm()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.SetExecutionMode(1); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := vm.LoadProgram(program); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmInput); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := vm.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
state, err := vm.GetExecutionState()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("HostAwait state: %s\n", state)
|
||||
|
||||
result, err := vm.Resume(`{"tier":"gold"}`, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("HostAwait result: %s\n", result)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package regorus
|
||||
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -lregorus_ffi
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -L ../../../ffi/target/debug -lregorus_ffi
|
||||
// #include "../../../ffi/regorus.h"
|
||||
import "C"
|
||||
import (
|
||||
@@ -214,3 +214,59 @@ func (e *Engine) TakePrints() (string, error) {
|
||||
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
type PolicyLengthConfig struct {
|
||||
MaxCol uint32
|
||||
MaxFileBytes uint
|
||||
MaxLines uint
|
||||
}
|
||||
|
||||
func (e *Engine) SetPolicyLengthConfig(config PolicyLengthConfig) error {
|
||||
c := C.RegorusPolicyLengthConfig{
|
||||
max_col: C.uint32_t(config.MaxCol),
|
||||
max_file_bytes: C.size_t(config.MaxFileBytes),
|
||||
max_lines: C.size_t(config.MaxLines),
|
||||
}
|
||||
result := C.regorus_engine_set_policy_length_config(e.e, c)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) ClearPolicyLengthConfig() error {
|
||||
result := C.regorus_engine_clear_policy_length_config(e.e)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CacheConfig struct {
|
||||
Regex uint
|
||||
Glob uint
|
||||
}
|
||||
|
||||
func SetCacheConfig(config CacheConfig) error {
|
||||
c := C.RegorusCacheConfig{
|
||||
regex: C.size_t(config.Regex),
|
||||
glob: C.size_t(config.Glob),
|
||||
}
|
||||
result := C.regorus_set_cache_config(c)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ClearCache() error {
|
||||
result := C.regorus_clear_cache()
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
283
bindings/go/pkg/regorus/rvm.go
Normal file
283
bindings/go/pkg/regorus/rvm.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package regorus
|
||||
|
||||
// #cgo LDFLAGS: -L ../../../ffi/target/release -L ../../../ffi/target/debug -lregorus_ffi
|
||||
// #include "../../../ffi/regorus.h"
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type PolicyModule struct {
|
||||
Id string
|
||||
Content string
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
p *C.RegorusProgram
|
||||
}
|
||||
|
||||
type Rvm struct {
|
||||
vm *C.RegorusRvm
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
b *C.RegorusBuffer
|
||||
}
|
||||
|
||||
func (b *Buffer) Close() {
|
||||
if b != nil && b.b != nil {
|
||||
C.regorus_buffer_drop(b.b)
|
||||
b.b = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
if b == nil || b.b == nil || b.b.data == nil || b.b.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return C.GoBytes(unsafe.Pointer(b.b.data), C.int(b.b.len))
|
||||
}
|
||||
|
||||
func (p *Program) Close() {
|
||||
if p != nil && p.p != nil {
|
||||
C.regorus_program_drop(p.p)
|
||||
p.p = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) SerializeBinary() ([]byte, error) {
|
||||
result := C.regorus_program_serialize_binary(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
buffer := &Buffer{b: (*C.RegorusBuffer)(result.pointer_value)}
|
||||
defer buffer.Close()
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *Program) GenerateListing() (string, error) {
|
||||
result := C.regorus_program_generate_listing(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (p *Program) GenerateTabularListing() (string, error) {
|
||||
result := C.regorus_program_generate_tabular_listing(p.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func DeserializeProgram(data []byte) (*Program, bool, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, false, fmt.Errorf("empty program data")
|
||||
}
|
||||
var isPartial C.bool
|
||||
result := C.regorus_program_deserialize_binary((*C.uchar)(unsafe.Pointer(&data[0])), C.ulong(len(data)), (*C.bool)(unsafe.Pointer(&isPartial)))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, false, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, bool(isPartial), nil
|
||||
}
|
||||
|
||||
func CompileProgramFromModules(data string, modules []PolicyModule, entryPoints []string) (*Program, error) {
|
||||
dataC := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
|
||||
cModules := make([]C.RegorusPolicyModule, len(modules))
|
||||
moduleIdPtrs := make([]*C.char, len(modules))
|
||||
moduleContentPtrs := make([]*C.char, len(modules))
|
||||
for i, module := range modules {
|
||||
idC := C.CString(module.Id)
|
||||
contentC := C.CString(module.Content)
|
||||
moduleIdPtrs[i] = idC
|
||||
moduleContentPtrs[i] = contentC
|
||||
cModules[i].id = idC
|
||||
cModules[i].content = contentC
|
||||
}
|
||||
defer func() {
|
||||
for i := range moduleIdPtrs {
|
||||
if moduleIdPtrs[i] != nil {
|
||||
C.free(unsafe.Pointer(moduleIdPtrs[i]))
|
||||
}
|
||||
if moduleContentPtrs[i] != nil {
|
||||
C.free(unsafe.Pointer(moduleContentPtrs[i]))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
entryPtrs := make([]*C.char, len(entryPoints))
|
||||
for i, entry := range entryPoints {
|
||||
entryPtrs[i] = C.CString(entry)
|
||||
}
|
||||
defer func() {
|
||||
for _, ptr := range entryPtrs {
|
||||
C.free(unsafe.Pointer(ptr))
|
||||
}
|
||||
}()
|
||||
|
||||
var modulesPtr *C.RegorusPolicyModule
|
||||
if len(cModules) > 0 {
|
||||
modulesPtr = (*C.RegorusPolicyModule)(unsafe.Pointer(&cModules[0]))
|
||||
}
|
||||
var entryPtr **C.char
|
||||
if len(entryPtrs) > 0 {
|
||||
entryPtr = (**C.char)(unsafe.Pointer(&entryPtrs[0]))
|
||||
}
|
||||
|
||||
result := C.regorus_program_compile_from_modules(
|
||||
dataC,
|
||||
modulesPtr,
|
||||
C.ulong(len(cModules)),
|
||||
entryPtr,
|
||||
C.ulong(len(entryPtrs)),
|
||||
)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, nil
|
||||
}
|
||||
|
||||
func CompileProgramFromEngine(engine *Engine, entryPoints []string) (*Program, error) {
|
||||
entryPtrs := make([]*C.char, len(entryPoints))
|
||||
for i, entry := range entryPoints {
|
||||
entryPtrs[i] = C.CString(entry)
|
||||
}
|
||||
defer func() {
|
||||
for _, ptr := range entryPtrs {
|
||||
C.free(unsafe.Pointer(ptr))
|
||||
}
|
||||
}()
|
||||
|
||||
var entryPtr **C.char
|
||||
if len(entryPtrs) > 0 {
|
||||
entryPtr = (**C.char)(unsafe.Pointer(&entryPtrs[0]))
|
||||
}
|
||||
|
||||
result := C.regorus_engine_compile_program_with_entrypoints(
|
||||
engine.e,
|
||||
entryPtr,
|
||||
C.ulong(len(entryPtrs)),
|
||||
)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return nil, fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return &Program{p: (*C.RegorusProgram)(result.pointer_value)}, nil
|
||||
}
|
||||
|
||||
func NewRvm() (*Rvm, error) {
|
||||
vm := C.regorus_rvm_new()
|
||||
if vm == nil {
|
||||
return nil, fmt.Errorf("failed to create RVM")
|
||||
}
|
||||
return &Rvm{vm: vm}, nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Close() {
|
||||
if r != nil && r.vm != nil {
|
||||
C.regorus_rvm_drop(r.vm)
|
||||
r.vm = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Rvm) LoadProgram(program *Program) error {
|
||||
result := C.regorus_rvm_load_program(r.vm, program.p)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetDataJson(data string) error {
|
||||
dataC := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(dataC))
|
||||
result := C.regorus_rvm_set_data(r.vm, dataC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetInputJson(input string) error {
|
||||
inputC := C.CString(input)
|
||||
defer C.free(unsafe.Pointer(inputC))
|
||||
result := C.regorus_rvm_set_input(r.vm, inputC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) SetExecutionMode(mode byte) error {
|
||||
result := C.regorus_rvm_set_execution_mode(r.vm, C.uchar(mode))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Execute() (string, error) {
|
||||
result := C.regorus_rvm_execute(r.vm)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) ExecuteEntryPoint(name string) (string, error) {
|
||||
nameC := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(nameC))
|
||||
result := C.regorus_rvm_execute_entry_point_by_name(r.vm, nameC)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) ExecuteEntryPointIndex(index uint64) (string, error) {
|
||||
result := C.regorus_rvm_execute_entry_point_by_index(r.vm, C.ulong(index))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) Resume(resumeValue string, hasValue bool) (string, error) {
|
||||
var valueC *C.char
|
||||
if hasValue {
|
||||
valueC = C.CString(resumeValue)
|
||||
defer C.free(unsafe.Pointer(valueC))
|
||||
}
|
||||
result := C.regorus_rvm_resume(r.vm, valueC, C.bool(hasValue))
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (r *Rvm) GetExecutionState() (string, error) {
|
||||
result := C.regorus_rvm_get_execution_state(r.vm)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.Ok {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
128
bindings/go/pkg/regorus/rvm_test.go
Normal file
128
bindings/go/pkg/regorus/rvm_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package regorus
|
||||
|
||||
import "testing"
|
||||
|
||||
const rvmPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.account.active == true
|
||||
details := __builtin_host_await(input.account.id, "account")
|
||||
details.tier == "gold"
|
||||
}
|
||||
`
|
||||
|
||||
const rvmInput = `{"account":{"id":"acct-1","active":true}}`
|
||||
|
||||
const rvmRegularPolicy = `
|
||||
package demo
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.user == "alice"
|
||||
input.active == true
|
||||
}
|
||||
`
|
||||
|
||||
const rvmRegularInput = `{"user":"alice","active":true}`
|
||||
|
||||
func TestRvmProgramCompileAndExecute(t *testing.T) {
|
||||
modules := []PolicyModule{{Id: "demo.rego", Content: rvmRegularPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
t.Fatalf("compile program: %v", err)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
listing, err := program.GenerateListing()
|
||||
if err != nil || listing == "" {
|
||||
t.Fatalf("listing failed: %v", err)
|
||||
}
|
||||
|
||||
binary, err := program.SerializeBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("serialize program: %v", err)
|
||||
}
|
||||
|
||||
rehydrated, isPartial, err := DeserializeProgram(binary)
|
||||
if err != nil {
|
||||
t.Fatalf("deserialize program: %v", err)
|
||||
}
|
||||
if isPartial {
|
||||
t.Fatalf("deserialized program marked partial")
|
||||
}
|
||||
defer rehydrated.Close()
|
||||
|
||||
vm, err := NewRvm()
|
||||
if err != nil {
|
||||
t.Fatalf("new vm: %v", err)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.LoadProgram(rehydrated); err != nil {
|
||||
t.Fatalf("load program: %v", err)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmRegularInput); err != nil {
|
||||
t.Fatalf("set input: %v", err)
|
||||
}
|
||||
|
||||
result, err := vm.Execute()
|
||||
if err != nil {
|
||||
t.Fatalf("execute: %v", err)
|
||||
}
|
||||
if result != "true" {
|
||||
t.Fatalf("expected allow=true, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRvmHostAwaitSuspendResume(t *testing.T) {
|
||||
modules := []PolicyModule{{Id: "host_await.rego", Content: rvmPolicy}}
|
||||
entryPoints := []string{"data.demo.allow"}
|
||||
program, err := CompileProgramFromModules("{}", modules, entryPoints)
|
||||
if err != nil {
|
||||
t.Fatalf("compile program: %v", err)
|
||||
}
|
||||
defer program.Close()
|
||||
|
||||
vm, err := NewRvm()
|
||||
if err != nil {
|
||||
t.Fatalf("new vm: %v", err)
|
||||
}
|
||||
defer vm.Close()
|
||||
|
||||
if err := vm.SetExecutionMode(1); err != nil {
|
||||
t.Fatalf("set execution mode: %v", err)
|
||||
}
|
||||
if err := vm.LoadProgram(program); err != nil {
|
||||
t.Fatalf("load program: %v", err)
|
||||
}
|
||||
if err := vm.SetInputJson(rvmInput); err != nil {
|
||||
t.Fatalf("set input: %v", err)
|
||||
}
|
||||
|
||||
if _, err := vm.Execute(); err != nil {
|
||||
t.Fatalf("execute in suspendable mode failed: %v", err)
|
||||
}
|
||||
|
||||
state, err := vm.GetExecutionState()
|
||||
if err != nil {
|
||||
t.Fatalf("get execution state: %v", err)
|
||||
}
|
||||
if state == "" {
|
||||
t.Fatalf("expected non-empty execution state")
|
||||
}
|
||||
|
||||
result, err := vm.Resume(`{"tier":"gold"}`, true)
|
||||
if err != nil {
|
||||
t.Fatalf("resume: %v", err)
|
||||
}
|
||||
if result != "true" {
|
||||
t.Fatalf("expected allow=true, got %s", result)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user