feat(bindings)!: add RVM/Program support across FFI and language bindings (#565)

- FFI: add RVM/Program APIs, execution state accessors, HostAwait handling, and buffer/result helpers in rvm.rs, common.rs, engine.rs.
- Compiler: emit HostAwait for __builtin_host_await in function_calls.rs.
- RVM tests: add HostAwait regression cases and extend harness for suspend/resume responses in host_await.yaml and mod.rs.
- C/C++: add RVM tests/examples and wrapper updates in rvm_tests.c, rvm_tests.cpp, regorus.hpp, plus CMake wiring.
- C#: add Program/Rvm bindings, SafeHandle/PInvoke, tests, and example usage in Regorus, RvmProgramTests.cs, Program.cs, and README updates.
- Go: add Program/Rvm bindings, tests, and examples in rvm.go, rvm_test.go, main.go.
- Java: add Program/Rvm bindings, JNI glue, and examples in lib.rs, regorus, Test.java.
- Python: add Program/Rvm bindings and examples in lib.rs, test.py.
- WASM: add Program/Rvm bindings and examples in lib.rs, test.js.
- Tooling: wire binding tests in xtask and ignore generated Java artifacts in .gitignore.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2026-01-30 23:55:31 +05:30
committed by GitHub
parent 0316ccd90c
commit 3f7a5496dc
50 changed files with 4990 additions and 110 deletions

View File

@@ -64,3 +64,43 @@ 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}");
```

View 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");
}
}

View File

@@ -180,7 +180,7 @@ namespace Regorus
return handle;
}
private T UseHandle<T>(Func<IntPtr, T> func)
internal T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
@@ -204,6 +204,11 @@ namespace Regorus
}
}
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
{
return UseHandle(func);
}
private void UseHandle(Action<IntPtr> action)
{
UseHandle<object?>(handlePtr =>

View File

@@ -441,7 +441,7 @@ namespace Regorus
}
}
private RegorusEngineHandle GetHandleForUse()
internal RegorusEngineHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
@@ -451,7 +451,7 @@ namespace Regorus
return handle;
}
private void UseHandle(Action<IntPtr> action)
internal void UseHandle(Action<IntPtr> action)
{
UseHandle<object?>(handlePtr =>
{
@@ -460,7 +460,7 @@ namespace Regorus
});
}
private T UseHandle<T>(Func<IntPtr, T> func)
internal T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
@@ -484,5 +484,10 @@ namespace Regorus
}
}
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
{
return UseHandle(func);
}
}
}

View File

@@ -26,6 +26,13 @@ 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
@@ -85,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>
@@ -92,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
@@ -617,6 +762,17 @@ namespace Regorus.Internal
public uint check_interval;
}
/// <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>
@@ -633,6 +789,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>

View File

@@ -0,0 +1,333 @@
// 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 : IDisposable
{
private RegorusProgramHandle? _handle;
private int _isDisposed;
private Program(RegorusProgramHandle handle)
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
/// <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)
{
var modulesArray = modules.ToArray();
var entryPointsArray = entryPoints.ToArray();
if (entryPointsArray.Length == 0)
{
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
}
var nativeModules = new RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2 + entryPointsArray.Length);
var entryPointers = new IntPtr[entryPointsArray.Length];
try
{
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 RegorusPolicyModule
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
for (int i = 0; i < entryPointsArray.Length; i++)
{
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
pinnedStrings.Add(entryPinned);
entryPointers[i] = (IntPtr)entryPinned.Pointer;
}
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
fixed (RegorusPolicyModule* modulesPtr = nativeModules)
fixed (IntPtr* entryPtr = entryPointers)
{
var result = API.regorus_program_compile_from_modules(
(byte*)dataPtr,
modulesPtr,
(UIntPtr)modulesArray.Length,
(byte**)entryPtr,
(UIntPtr)entryPointsArray.Length);
return GetProgramResult(result);
}
});
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
}
/// <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));
}
var entryPointsArray = entryPoints.ToArray();
if (entryPointsArray.Length == 0)
{
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
}
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(entryPointsArray.Length);
var entryPointers = new IntPtr[entryPointsArray.Length];
try
{
for (int i = 0; i < entryPointsArray.Length; i++)
{
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
pinnedStrings.Add(entryPinned);
entryPointers[i] = (IntPtr)entryPinned.Pointer;
}
return engine.UseHandleForInterop(enginePtr =>
{
fixed (IntPtr* entryPtr = entryPointers)
{
var result = API.regorus_engine_compile_program_with_entrypoints(
(RegorusEngine*)enginePtr,
(byte**)entryPtr,
(UIntPtr)entryPointsArray.Length);
return GetProgramResult(result);
}
});
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
}
/// <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()
{
ThrowIfDisposed();
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()
{
ThrowIfDisposed();
return UseHandle(programPtr =>
{
return CheckAndDropResult(API.regorus_program_generate_listing((RegorusProgram*)programPtr));
});
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
_handle?.Dispose();
_handle = null;
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
{
throw new ObjectDisposedException(nameof(Program));
}
}
internal RegorusProgramHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(Program));
}
return handle;
}
internal 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(Program));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
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)
{
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);
}
}
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);
}
}
}
}

View File

@@ -0,0 +1,292 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Wrapper for the Regorus RVM runtime.
/// </summary>
public unsafe sealed class Rvm : IDisposable
{
private RegorusRvmHandle? _handle;
private int _isDisposed;
public Rvm()
{
_handle = RegorusRvmHandle.Create();
}
private Rvm(RegorusRvmHandle handle)
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
/// <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)
{
ThrowIfDisposed();
if (program is null)
{
throw new ArgumentNullException(nameof(program));
}
program.UseHandle(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)
{
ThrowIfDisposed();
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)
{
ThrowIfDisposed();
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)
{
ThrowIfDisposed();
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_execution_mode((RegorusRvm*)vmPtr, mode));
return 0;
});
}
/// <summary>
/// Execute the program and return the JSON result.
/// </summary>
public string? Execute()
{
ThrowIfDisposed();
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_execute((RegorusRvm*)vmPtr));
});
}
/// <summary>
/// Execute a named entry point.
/// </summary>
public string? ExecuteEntryPoint(string entryPoint)
{
ThrowIfDisposed();
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)
{
ThrowIfDisposed();
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)
{
ThrowIfDisposed();
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()
{
ThrowIfDisposed();
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_get_execution_state((RegorusRvm*)vmPtr));
});
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
_handle?.Dispose();
_handle = null;
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
{
throw new ObjectDisposedException(nameof(Rvm));
}
}
internal RegorusRvmHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(Rvm));
}
return handle;
}
internal 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(Rvm));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
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)
{
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);
}
}
}
}

View File

@@ -87,4 +87,100 @@ 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 && !IsClosed)
{
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 && !IsClosed)
{
unsafe
{
Internal.API.regorus_rvm_drop((Internal.RegorusRvm*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
}

View File

@@ -65,6 +65,55 @@ triplet_count := count([1 |
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"",
@@ -174,6 +223,15 @@ triplet_count := count([1 |
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)
@@ -359,4 +417,77 @@ triplet_count := count([1 |
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}");
}
}