mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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:
committed by
GitHub
parent
0316ccd90c
commit
3f7a5496dc
@@ -41,3 +41,10 @@ bindings/ruby/bin/
|
|||||||
|
|
||||||
# JetBrains IDEs files
|
# JetBrains IDEs files
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Java build artifacts
|
||||||
|
**/*.class
|
||||||
|
**/*.jar
|
||||||
|
bindings/java/.classpath
|
||||||
|
bindings/java/.project
|
||||||
|
bindings/java/.settings/
|
||||||
|
|||||||
Generated
+16
-16
@@ -196,9 +196,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.54"
|
version = "1.2.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -262,9 +262,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.54"
|
version = "4.5.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -272,9 +272,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.54"
|
version = "4.5.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -284,9 +284,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.49"
|
version = "4.5.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
@@ -460,9 +460,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
@@ -571,9 +571,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.64"
|
version = "0.1.65"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -1831,18 +1831,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.44",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ FetchContent_Declare(
|
|||||||
FetchContent_MakeAvailable(Corrosion)
|
FetchContent_MakeAvailable(Corrosion)
|
||||||
|
|
||||||
project("regorus-test")
|
project("regorus-test")
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
corrosion_import_crate(
|
corrosion_import_crate(
|
||||||
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
|
# 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
|
# Add path to <regorus-source-folder>/bindings/ffi
|
||||||
target_include_directories(regorus_test PRIVATE "../ffi")
|
target_include_directories(regorus_test PRIVATE "../ffi")
|
||||||
target_link_libraries(regorus_test regorus_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)
|
||||||
|
|||||||
@@ -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")
|
project("regorus-test")
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
# installable ffi target
|
# installable ffi target
|
||||||
|
|
||||||
@@ -83,3 +84,9 @@ install(FILES
|
|||||||
|
|
||||||
add_executable(regorus_test main.cpp)
|
add_executable(regorus_test main.cpp)
|
||||||
target_link_libraries(regorus_test regorus_ffi::regorus_ffi)
|
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)
|
||||||
|
|||||||
+276
-5
@@ -1,6 +1,8 @@
|
|||||||
#ifndef REGORUS_WRAPPER_HPP
|
#ifndef REGORUS_WRAPPER_HPP
|
||||||
#define REGORUS_WRAPPER_HPP
|
#define REGORUS_WRAPPER_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
@@ -8,8 +10,11 @@
|
|||||||
|
|
||||||
namespace regorus {
|
namespace regorus {
|
||||||
|
|
||||||
class Result {
|
class Buffer;
|
||||||
public:
|
class Program;
|
||||||
|
|
||||||
|
class Result {
|
||||||
|
public:
|
||||||
|
|
||||||
operator bool() const { return result.status == RegorusStatus::Ok; }
|
operator bool() const { return result.status == RegorusStatus::Ok; }
|
||||||
bool operator !() 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() {
|
~Result() {
|
||||||
regorus_result_drop(result);
|
regorus_result_drop(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Engine;
|
|
||||||
RegorusResult result;
|
RegorusResult result;
|
||||||
|
|
||||||
Result(RegorusResult r) : result(r) {}
|
|
||||||
private:
|
private:
|
||||||
Result(const Result&) = delete;
|
Result(const Result&) = delete;
|
||||||
Result(Result&&) = delete;
|
|
||||||
Result& operator=(const Result&) = delete;
|
Result& operator=(const Result&) = delete;
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -109,6 +135,10 @@ namespace regorus {
|
|||||||
~Engine() {
|
~Engine() {
|
||||||
regorus_engine_drop(engine);
|
regorus_engine_drop(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RegorusEngine* raw() const {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -119,6 +149,247 @@ namespace regorus {
|
|||||||
Engine(Engine&&) = delete;
|
Engine(Engine&&) = delete;
|
||||||
Engine& operator=(const Engine&) = delete;
|
Engine& operator=(const Engine&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
#endif // REGORUS_WRAPPER_HPP
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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.
|
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}");
|
||||||
|
```
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -180,7 +180,7 @@ namespace Regorus
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private T UseHandle<T>(Func<IntPtr, T> func)
|
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||||
{
|
{
|
||||||
var handle = GetHandleForUse();
|
var handle = GetHandleForUse();
|
||||||
bool addedRef = false;
|
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)
|
private void UseHandle(Action<IntPtr> action)
|
||||||
{
|
{
|
||||||
UseHandle<object?>(handlePtr =>
|
UseHandle<object?>(handlePtr =>
|
||||||
|
|||||||
@@ -441,7 +441,7 @@ namespace Regorus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RegorusEngineHandle GetHandleForUse()
|
internal RegorusEngineHandle GetHandleForUse()
|
||||||
{
|
{
|
||||||
var handle = _handle;
|
var handle = _handle;
|
||||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||||
@@ -451,7 +451,7 @@ namespace Regorus
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UseHandle(Action<IntPtr> action)
|
internal void UseHandle(Action<IntPtr> action)
|
||||||
{
|
{
|
||||||
UseHandle<object?>(handlePtr =>
|
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();
|
var handle = GetHandleForUse();
|
||||||
bool addedRef = false;
|
bool addedRef = false;
|
||||||
@@ -484,5 +484,10 @@ namespace Regorus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
|
||||||
|
{
|
||||||
|
return UseHandle(func);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ namespace Regorus.Internal
|
|||||||
[DllImport(LibraryName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(LibraryName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern void regorus_result_drop(RegorusResult result);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Memory Limit Methods
|
#region Memory Limit Methods
|
||||||
@@ -85,6 +92,12 @@ namespace Regorus.Internal
|
|||||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
|
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>
|
/// <summary>
|
||||||
/// Drop a RegorusEngine.
|
/// Drop a RegorusEngine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -92,6 +105,138 @@ namespace Regorus.Internal
|
|||||||
internal static extern void regorus_engine_drop(RegorusEngine* engine);
|
internal static extern void regorus_engine_drop(RegorusEngine* engine);
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Add a policy.
|
||||||
/// The policy is parsed into AST.
|
/// The policy is parsed into AST.
|
||||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
|
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
|
||||||
@@ -617,6 +762,17 @@ namespace Regorus.Internal
|
|||||||
public uint check_interval;
|
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>
|
/// <summary>
|
||||||
/// Wrapper for regorus::Engine.
|
/// Wrapper for regorus::Engine.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// FFI wrapper for PolicyModule struct.
|
/// FFI wrapper for PolicyModule struct.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,4 +87,100 @@ namespace Regorus
|
|||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,55 @@ triplet_count := count([1 |
|
|||||||
private const string EXECUTION_TIMER_QUERY = "data.limits.timer.triplet_count";
|
private const string EXECUTION_TIMER_QUERY = "data.limits.timer.triplet_count";
|
||||||
private const int EXECUTION_TIMER_VALUE_COUNT = 40;
|
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
|
// Test data constants
|
||||||
private const string COMPLIANT_STORAGE_ACCOUNT = @"{
|
private const string COMPLIANT_STORAGE_ACCOUNT = @"{
|
||||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||||
@@ -174,6 +223,15 @@ triplet_count := count([1 |
|
|||||||
|
|
||||||
Console.WriteLine("\n5. Execution timer configuration:");
|
Console.WriteLine("\n5. Execution timer configuration:");
|
||||||
DemonstrateExecutionTimer();
|
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)
|
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
|
||||||
@@ -359,4 +417,77 @@ triplet_count := count([1 |
|
|||||||
Regorus.Engine.ClearFallbackExecutionTimerConfig();
|
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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+34
-14
@@ -102,6 +102,16 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"unty",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -172,9 +182,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.54"
|
version = "1.2.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -211,18 +221,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.54"
|
version = "4.5.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.54"
|
version = "4.5.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -329,9 +339,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fluent-uri"
|
name = "fluent-uri"
|
||||||
@@ -407,9 +417,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.64"
|
version = "0.1.65"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -539,6 +549,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -941,11 +953,13 @@ name = "regorus"
|
|||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"jsonschema",
|
"jsonschema",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -1244,6 +1258,12 @@ version = "0.2.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unty"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -1468,18 +1488,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ default = [
|
|||||||
"std",
|
"std",
|
||||||
"coverage",
|
"coverage",
|
||||||
"allocator-memory-limits",
|
"allocator-memory-limits",
|
||||||
|
"rvm",
|
||||||
"regorus/arc",
|
"regorus/arc",
|
||||||
"regorus/full-opa",
|
"regorus/full-opa",
|
||||||
"contention_checks",
|
"contention_checks",
|
||||||
@@ -40,6 +41,7 @@ std = ["regorus/std"]
|
|||||||
coverage = ["regorus/coverage"]
|
coverage = ["regorus/coverage"]
|
||||||
allocator-memory-limits = ["regorus/allocator-memory-limits"]
|
allocator-memory-limits = ["regorus/allocator-memory-limits"]
|
||||||
contention_checks = ["parking_lot"]
|
contention_checks = ["parking_lot"]
|
||||||
|
rvm = ["regorus/rvm"]
|
||||||
custom_allocator = []
|
custom_allocator = []
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
use alloc::ffi::CString;
|
use alloc::ffi::CString;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
|
use alloc::vec::Vec;
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use core::ffi::{c_char, c_longlong, c_void, CStr};
|
use core::ffi::{c_char, c_longlong, c_void, CStr};
|
||||||
use core::ptr;
|
use core::{mem, ptr};
|
||||||
|
|
||||||
/// Status of a call on `RegorusEngine`.
|
/// Status of a call on `RegorusEngine`.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -90,6 +92,19 @@ pub struct RegorusResult {
|
|||||||
pub(crate) error_message: *mut c_char,
|
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 {
|
impl RegorusResult {
|
||||||
/// Create a successful result with no data.
|
/// Create a successful result with no data.
|
||||||
pub(crate) fn ok_void() -> Self {
|
pub(crate) fn ok_void() -> Self {
|
||||||
@@ -185,6 +200,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 {
|
pub(crate) fn to_c_str(s: String) -> *mut c_char {
|
||||||
match CString::new(s) {
|
match CString::new(s) {
|
||||||
Ok(cs) => cs.into_raw(),
|
Ok(cs) => cs.into_raw(),
|
||||||
@@ -222,6 +249,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`.
|
/// Drop a `RegorusResult`.
|
||||||
///
|
///
|
||||||
/// `output` and `error_message` strings are not valid after drop.
|
/// `output` and `error_message` strings are not valid after drop.
|
||||||
|
|||||||
@@ -11,9 +11,16 @@ use crate::panic_guard::with_unwind_guard;
|
|||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
|
#[cfg(feature = "rvm")]
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use alloc::vec::Vec;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use core::ffi::{c_char, c_void};
|
use core::ffi::{c_char, c_void};
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
#[cfg(feature = "rvm")]
|
||||||
|
use regorus::languages::rego::compiler::Compiler;
|
||||||
|
#[cfg(feature = "rvm")]
|
||||||
|
use regorus::rvm::program::Program;
|
||||||
|
|
||||||
/// Wrapper for `regorus::Engine`.
|
/// Wrapper for `regorus::Engine`.
|
||||||
pub struct RegorusEngine {
|
pub struct RegorusEngine {
|
||||||
@@ -720,3 +727,65 @@ pub extern "C" fn regorus_engine_compile_with_entrypoint(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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}"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,5 +14,7 @@ mod engine;
|
|||||||
mod limits;
|
mod limits;
|
||||||
mod lock;
|
mod lock;
|
||||||
mod panic_guard;
|
mod panic_guard;
|
||||||
|
#[cfg(feature = "rvm")]
|
||||||
|
pub(crate) mod rvm;
|
||||||
mod schema_registry;
|
mod schema_registry;
|
||||||
mod target_registry;
|
mod target_registry;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -100,4 +100,138 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Printf("%s\n", output)
|
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
|
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"
|
// #include "../../../ffi/regorus.h"
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
Generated
+30
-10
@@ -52,6 +52,16 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"unty",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -109,9 +119,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.54"
|
version = "1.2.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -213,9 +223,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fluent-uri"
|
name = "fluent-uri"
|
||||||
@@ -279,9 +289,9 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.64"
|
version = "0.1.65"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -411,6 +421,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -817,10 +829,12 @@ name = "regorus"
|
|||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"jsonschema",
|
"jsonschema",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -1067,6 +1081,12 @@ version = "0.2.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unty"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -1364,18 +1384,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ ast = ["regorus/ast"]
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
serde_json = "1.0.112"
|
serde_json = "1.0.112"
|
||||||
jni = "0.21.1"
|
jni = "0.21.1"
|
||||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
import com.microsoft.regorus.Engine;
|
import com.microsoft.regorus.Engine;
|
||||||
|
import com.microsoft.regorus.PolicyModule;
|
||||||
|
import com.microsoft.regorus.Program;
|
||||||
|
import com.microsoft.regorus.Rvm;
|
||||||
|
|
||||||
public class Test {
|
public class Test {
|
||||||
|
|
||||||
@@ -44,5 +47,70 @@ public class Test {
|
|||||||
"package world\nx { true }"
|
"package world\nx { true }"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String regularPolicy = String.join("\n",
|
||||||
|
"package demo",
|
||||||
|
"import rego.v1",
|
||||||
|
"",
|
||||||
|
"default allow := false",
|
||||||
|
"",
|
||||||
|
"allow if {",
|
||||||
|
" input.user == \"alice\"",
|
||||||
|
" input.active == true",
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
String regularInput = "{\"user\":\"alice\",\"active\":true}";
|
||||||
|
|
||||||
|
{
|
||||||
|
PolicyModule module = new PolicyModule("demo.rego", regularPolicy);
|
||||||
|
Program program = Program.compileFromModules("{}", new PolicyModule[]{module}, new String[]{"data.demo.allow"});
|
||||||
|
System.out.println("RVM listing:\n" + program.generateListing());
|
||||||
|
|
||||||
|
byte[] binary = program.serializeBinary();
|
||||||
|
program.close();
|
||||||
|
|
||||||
|
boolean[] isPartial = new boolean[1];
|
||||||
|
Program rehydrated = Program.deserializeBinary(binary, isPartial);
|
||||||
|
if (isPartial[0]) {
|
||||||
|
throw new IllegalStateException("Deserialized program marked partial");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Rvm vm = new Rvm()) {
|
||||||
|
vm.loadProgram(rehydrated);
|
||||||
|
vm.setInputJson(regularInput);
|
||||||
|
String result = vm.execute();
|
||||||
|
System.out.println("RVM regular result: " + result);
|
||||||
|
}
|
||||||
|
rehydrated.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
String awaitPolicy = String.join("\n",
|
||||||
|
"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\"",
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
String awaitInput = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
|
||||||
|
|
||||||
|
{
|
||||||
|
PolicyModule module = new PolicyModule("await.rego", awaitPolicy);
|
||||||
|
Program program = Program.compileFromModules("{}", new PolicyModule[]{module}, new String[]{"data.demo.allow"});
|
||||||
|
try (Rvm vm = new Rvm()) {
|
||||||
|
vm.setExecutionMode((byte) 1);
|
||||||
|
vm.loadProgram(program);
|
||||||
|
vm.setInputJson(awaitInput);
|
||||||
|
vm.execute();
|
||||||
|
System.out.println("HostAwait state: " + vm.getExecutionState());
|
||||||
|
String resumed = vm.resume("{\"tier\":\"gold\"}");
|
||||||
|
System.out.println("HostAwait result: " + resumed);
|
||||||
|
}
|
||||||
|
program.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+355
-3
@@ -2,11 +2,17 @@
|
|||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use jni::objects::{JClass, JObject, JString};
|
use jni::objects::{JBooleanArray, JByteArray, JClass, JObject, JObjectArray, JString};
|
||||||
use jni::sys::{jlong, jstring};
|
use jni::sys::{jboolean, jbooleanArray, jbyteArray, jlong, jobjectArray, jstring};
|
||||||
use jni::JNIEnv;
|
use jni::JNIEnv;
|
||||||
|
|
||||||
use regorus::{Engine, Value};
|
use regorus::languages::rego::compiler::Compiler;
|
||||||
|
use regorus::rvm::program::{
|
||||||
|
generate_assembly_listing, AssemblyListingConfig, DeserializationResult, Program as RvmProgram,
|
||||||
|
};
|
||||||
|
use regorus::rvm::vm::{ExecutionMode, RegoVM};
|
||||||
|
use regorus::{compile_policy_with_entrypoint, Engine, PolicyModule, Rc, Value};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
|
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
|
||||||
@@ -370,6 +376,336 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModules(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
data_json: JString,
|
||||||
|
module_ids: jobjectArray,
|
||||||
|
module_contents: jobjectArray,
|
||||||
|
entry_points: jobjectArray,
|
||||||
|
) -> jlong {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let data_json: String = env.get_string(&data_json)?.into();
|
||||||
|
let data = Value::from_json_str(&data_json)?;
|
||||||
|
|
||||||
|
let ids = get_string_array(env, module_ids)?;
|
||||||
|
let contents = get_string_array(env, module_contents)?;
|
||||||
|
if ids.len() != contents.len() {
|
||||||
|
return Err(anyhow::anyhow!("module id/content length mismatch"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut modules = Vec::with_capacity(ids.len());
|
||||||
|
for (id, content) in ids.into_iter().zip(contents.into_iter()) {
|
||||||
|
modules.push(PolicyModule {
|
||||||
|
id: Rc::from(id.as_str()),
|
||||||
|
content: Rc::from(content.as_str()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry_points_vec = get_string_array(env, entry_points)?;
|
||||||
|
if entry_points_vec.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"entry_points must contain at least one entry"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||||
|
let entry_rule = entry_points_ref[0];
|
||||||
|
|
||||||
|
let compiled = compile_policy_with_entrypoint(data, &modules, Rc::from(entry_rule))?;
|
||||||
|
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
|
||||||
|
Ok(Box::into_raw(Box::new(program)) as jlong)
|
||||||
|
});
|
||||||
|
|
||||||
|
res.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromEngine(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
engine_ptr: jlong,
|
||||||
|
entry_points: jobjectArray,
|
||||||
|
) -> jlong {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||||
|
let entry_points_vec = get_string_array(env, entry_points)?;
|
||||||
|
if entry_points_vec.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"entry_points must contain at least one entry"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||||
|
let entry_rule = Rc::from(entry_points_ref[0]);
|
||||||
|
let compiled = engine.compile_with_entrypoint(&entry_rule)?;
|
||||||
|
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
|
||||||
|
Ok(Box::into_raw(Box::new(program)) as jlong)
|
||||||
|
});
|
||||||
|
|
||||||
|
res.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeGenerateListing(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
program_ptr: jlong,
|
||||||
|
) -> jstring {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
|
||||||
|
let listing =
|
||||||
|
generate_assembly_listing(program.as_ref(), &AssemblyListingConfig::default());
|
||||||
|
let output = env.new_string(&listing)?;
|
||||||
|
Ok(output.into_raw())
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => JObject::null().into_raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeSerializeBinary(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
program_ptr: jlong,
|
||||||
|
) -> jbyteArray {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
|
||||||
|
let bytes = program.serialize_binary().map_err(|e| anyhow::anyhow!(e))?;
|
||||||
|
let array = env.byte_array_from_slice(&bytes)?;
|
||||||
|
Ok(array.into_raw())
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => JObject::null().into_raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The `data` and `is_partial` pointers must be valid JNI array references
|
||||||
|
/// for the duration of the call. They must come from the JVM for the current
|
||||||
|
/// thread and not be used after this function returns.
|
||||||
|
pub unsafe extern "system" fn Java_com_microsoft_regorus_Program_nativeDeserializeBinary(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
data: jbyteArray,
|
||||||
|
is_partial: jbooleanArray,
|
||||||
|
) -> jlong {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
if data.is_null() {
|
||||||
|
return Err(anyhow::anyhow!("data must not be null"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = unsafe { JByteArray::from_raw(data) };
|
||||||
|
let bytes = env.convert_byte_array(&data)?;
|
||||||
|
let (program, partial) =
|
||||||
|
match RvmProgram::deserialize_binary(&bytes).map_err(|e| anyhow::anyhow!(e))? {
|
||||||
|
DeserializationResult::Complete(program) => (program, false),
|
||||||
|
DeserializationResult::Partial(program) => (program, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_partial.is_null() {
|
||||||
|
let is_partial = unsafe { JBooleanArray::from_raw(is_partial) };
|
||||||
|
let len = env.get_array_length(&is_partial)?;
|
||||||
|
if len > 0 {
|
||||||
|
let value: [jboolean; 1] = [if partial { 1 } else { 0 }];
|
||||||
|
env.set_boolean_array_region(&is_partial, 0, &value)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::into_raw(Box::new(Arc::new(program))) as jlong)
|
||||||
|
});
|
||||||
|
|
||||||
|
res.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeDrop(
|
||||||
|
_env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
program_ptr: jlong,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
let _program = Box::from_raw(program_ptr as *mut Arc<RvmProgram>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeNew(
|
||||||
|
_env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
) -> jlong {
|
||||||
|
let vm = RegoVM::new();
|
||||||
|
Box::into_raw(Box::new(vm)) as jlong
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeLoadProgram(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
program_ptr: jlong,
|
||||||
|
) {
|
||||||
|
let _ = throw_err(env, |_env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
|
||||||
|
vm.load_program(program.clone());
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetDataJson(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
data_json: JString,
|
||||||
|
) {
|
||||||
|
let _ = throw_err(env, |env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let data_json: String = env.get_string(&data_json)?.into();
|
||||||
|
let data = Value::from_json_str(&data_json)?;
|
||||||
|
vm.set_data(data)?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetInputJson(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
input_json: JString,
|
||||||
|
) {
|
||||||
|
let _ = throw_err(env, |env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let input_json: String = env.get_string(&input_json)?.into();
|
||||||
|
let input = Value::from_json_str(&input_json)?;
|
||||||
|
vm.set_input(input);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetExecutionMode(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
mode: u8,
|
||||||
|
) {
|
||||||
|
let _ = throw_err(env, |_env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let mode = match mode {
|
||||||
|
0 => ExecutionMode::RunToCompletion,
|
||||||
|
1 => ExecutionMode::Suspendable,
|
||||||
|
_ => return Err(anyhow::anyhow!("invalid execution mode")),
|
||||||
|
};
|
||||||
|
vm.set_execution_mode(mode);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecute(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
) -> jstring {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let result = vm.execute()?;
|
||||||
|
let output = env.new_string(result.to_json_str()?)?;
|
||||||
|
Ok(output.into_raw())
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => JObject::null().into_raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecuteEntryPoint(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
entry_point: JString,
|
||||||
|
) -> jstring {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let entry_point: String = env.get_string(&entry_point)?.into();
|
||||||
|
let result = vm.execute_entry_point_by_name(&entry_point)?;
|
||||||
|
let output = env.new_string(result.to_json_str()?)?;
|
||||||
|
Ok(output.into_raw())
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => JObject::null().into_raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeResume(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
resume_json: JString,
|
||||||
|
has_value: bool,
|
||||||
|
) -> jstring {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let value = if has_value {
|
||||||
|
let resume_json: String = env.get_string(&resume_json)?.into();
|
||||||
|
Some(Value::from_json_str(&resume_json)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let result = vm.resume(value)?;
|
||||||
|
let output = env.new_string(result.to_json_str()?)?;
|
||||||
|
Ok(output.into_raw())
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => JObject::null().into_raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeGetExecutionState(
|
||||||
|
env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
) -> jstring {
|
||||||
|
let res = throw_err(env, |env| {
|
||||||
|
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
|
||||||
|
let output = env.new_string(format!("{:?}", vm.execution_state()))?;
|
||||||
|
Ok(output.into_raw())
|
||||||
|
});
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => JObject::null().into_raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeDrop(
|
||||||
|
_env: JNIEnv,
|
||||||
|
_class: JClass,
|
||||||
|
vm_ptr: jlong,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
let _vm = Box::from_raw(vm_ptr as *mut RegoVM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) -> Result<T> {
|
fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) -> Result<T> {
|
||||||
match f(&mut env) {
|
match f(&mut env) {
|
||||||
Ok(val) => Ok(val),
|
Ok(val) => Ok(val),
|
||||||
@@ -379,3 +715,19 @@ fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_string_array(env: &mut JNIEnv, array: jobjectArray) -> Result<Vec<String>> {
|
||||||
|
if array.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
let array = unsafe { JObjectArray::from_raw(array) };
|
||||||
|
let len = env.get_array_length(&array)?;
|
||||||
|
let mut values = Vec::with_capacity(len as usize);
|
||||||
|
for i in 0..len {
|
||||||
|
let obj = env.get_object_array_element(&array, i)?;
|
||||||
|
let jstr = JString::from(obj);
|
||||||
|
let value: String = env.get_string(&jstr)?.into();
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
Ok(values)
|
||||||
|
}
|
||||||
|
|||||||
@@ -221,6 +221,8 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get coverage report as json string.
|
* Get coverage report as json string.
|
||||||
|
*
|
||||||
|
* @return Coverage report as a JSON string.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public String getCoverageReport() {
|
public String getCoverageReport() {
|
||||||
@@ -229,6 +231,8 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get coverage report as ANSI color coded string.
|
* Get coverage report as ANSI color coded string.
|
||||||
|
*
|
||||||
|
* @return Coverage report formatted for console output.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public String getCoverageReportPretty() {
|
public String getCoverageReportPretty() {
|
||||||
@@ -247,12 +251,18 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Take gathered prints.
|
* Take gathered prints.
|
||||||
|
*
|
||||||
|
* @return Collected print output as JSON.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public String takePrints() {
|
public String takePrints() {
|
||||||
return nativeTakePrints(enginePtr);
|
return nativeTakePrints(enginePtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long getPtr() {
|
||||||
|
return enginePtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
package com.microsoft.regorus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Rego module used for RVM program compilation.
|
||||||
|
*/
|
||||||
|
public final class PolicyModule {
|
||||||
|
/**
|
||||||
|
* Module identifier or filename.
|
||||||
|
*/
|
||||||
|
public final String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rego policy content.
|
||||||
|
*/
|
||||||
|
public final String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new policy module.
|
||||||
|
*
|
||||||
|
* @param id Module identifier or filename.
|
||||||
|
* @param content Rego policy content.
|
||||||
|
*/
|
||||||
|
public PolicyModule(String id, String content) {
|
||||||
|
this.id = id;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
package com.microsoft.regorus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a compiled RVM program.
|
||||||
|
*/
|
||||||
|
public final class Program implements AutoCloseable {
|
||||||
|
private static native long nativeCompileFromModules(
|
||||||
|
String dataJson,
|
||||||
|
String[] moduleIds,
|
||||||
|
String[] moduleContents,
|
||||||
|
String[] entryPoints);
|
||||||
|
|
||||||
|
private static native long nativeCompileFromEngine(long enginePtr, String[] entryPoints);
|
||||||
|
private static native String nativeGenerateListing(long programPtr);
|
||||||
|
private static native byte[] nativeSerializeBinary(long programPtr);
|
||||||
|
private static native long nativeDeserializeBinary(byte[] data, boolean[] isPartial);
|
||||||
|
private static native void nativeDrop(long programPtr);
|
||||||
|
|
||||||
|
private final long programPtr;
|
||||||
|
|
||||||
|
Program(long ptr) {
|
||||||
|
this.programPtr = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile a program from modules and entry points.
|
||||||
|
*
|
||||||
|
* @param dataJson JSON document to merge as static data.
|
||||||
|
* @param modules Policy modules to compile.
|
||||||
|
* @param entryPoints Entry point rule paths.
|
||||||
|
* @return Compiled program instance.
|
||||||
|
*/
|
||||||
|
public static Program compileFromModules(String dataJson, PolicyModule[] modules, String[] entryPoints) {
|
||||||
|
String[] ids = new String[modules.length];
|
||||||
|
String[] contents = new String[modules.length];
|
||||||
|
for (int i = 0; i < modules.length; i++) {
|
||||||
|
ids[i] = modules[i].id;
|
||||||
|
contents[i] = modules[i].content;
|
||||||
|
}
|
||||||
|
long ptr = nativeCompileFromModules(dataJson, ids, contents, entryPoints);
|
||||||
|
return new Program(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile a program from an engine and entry points.
|
||||||
|
*
|
||||||
|
* @param engine Engine with loaded policies.
|
||||||
|
* @param entryPoints Entry point rule paths.
|
||||||
|
* @return Compiled program instance.
|
||||||
|
*/
|
||||||
|
public static Program compileFromEngine(Engine engine, String[] entryPoints) {
|
||||||
|
long ptr = nativeCompileFromEngine(engine.getPtr(), entryPoints);
|
||||||
|
return new Program(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a readable assembly listing.
|
||||||
|
*
|
||||||
|
* @return Listing text.
|
||||||
|
*/
|
||||||
|
public String generateListing() {
|
||||||
|
return nativeGenerateListing(programPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize the program to binary format.
|
||||||
|
*
|
||||||
|
* @return Serialized bytes.
|
||||||
|
*/
|
||||||
|
public byte[] serializeBinary() {
|
||||||
|
return nativeSerializeBinary(programPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize a program from binary format.
|
||||||
|
*
|
||||||
|
* @param data Serialized program bytes.
|
||||||
|
* @param isPartial Optional array to receive the partial flag (index 0).
|
||||||
|
* @return Deserialized program instance.
|
||||||
|
*/
|
||||||
|
public static Program deserializeBinary(byte[] data, boolean[] isPartial) {
|
||||||
|
if (data == null || data.length == 0) {
|
||||||
|
throw new IllegalArgumentException("data must not be empty");
|
||||||
|
}
|
||||||
|
long ptr = nativeDeserializeBinary(data, isPartial);
|
||||||
|
return new Program(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
long getPtr() {
|
||||||
|
return programPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
nativeDrop(programPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
**/
|
||||||
|
|
||||||
|
package com.microsoft.regorus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the Regorus RVM runtime.
|
||||||
|
*/
|
||||||
|
public final class Rvm implements AutoCloseable {
|
||||||
|
private static native long nativeNew();
|
||||||
|
private static native void nativeDrop(long vmPtr);
|
||||||
|
private static native void nativeLoadProgram(long vmPtr, long programPtr);
|
||||||
|
private static native void nativeSetDataJson(long vmPtr, String dataJson);
|
||||||
|
private static native void nativeSetInputJson(long vmPtr, String inputJson);
|
||||||
|
private static native void nativeSetExecutionMode(long vmPtr, byte mode);
|
||||||
|
private static native String nativeExecute(long vmPtr);
|
||||||
|
private static native String nativeExecuteEntryPoint(long vmPtr, String entryPoint);
|
||||||
|
private static native String nativeResume(long vmPtr, String resumeJson, boolean hasValue);
|
||||||
|
private static native String nativeGetExecutionState(long vmPtr);
|
||||||
|
|
||||||
|
private final long vmPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new RVM instance.
|
||||||
|
*/
|
||||||
|
public Rvm() {
|
||||||
|
this.vmPtr = nativeNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a program into the VM.
|
||||||
|
*
|
||||||
|
* @param program Compiled program.
|
||||||
|
*/
|
||||||
|
public void loadProgram(Program program) {
|
||||||
|
nativeLoadProgram(vmPtr, program.getPtr());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set data JSON for the VM.
|
||||||
|
*
|
||||||
|
* @param dataJson JSON data document.
|
||||||
|
*/
|
||||||
|
public void setDataJson(String dataJson) {
|
||||||
|
nativeSetDataJson(vmPtr, dataJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set input JSON for the VM.
|
||||||
|
*
|
||||||
|
* @param inputJson JSON input document.
|
||||||
|
*/
|
||||||
|
public void setInputJson(String inputJson) {
|
||||||
|
nativeSetInputJson(vmPtr, inputJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set execution mode (0 = run-to-completion, 1 = suspendable).
|
||||||
|
*
|
||||||
|
* @param mode Execution mode.
|
||||||
|
*/
|
||||||
|
public void setExecutionMode(byte mode) {
|
||||||
|
nativeSetExecutionMode(vmPtr, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the program.
|
||||||
|
*
|
||||||
|
* @return JSON result string.
|
||||||
|
*/
|
||||||
|
public String execute() {
|
||||||
|
return nativeExecute(vmPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a named entry point.
|
||||||
|
*
|
||||||
|
* @param entryPoint Entry point rule path.
|
||||||
|
* @return JSON result string.
|
||||||
|
*/
|
||||||
|
public String executeEntryPoint(String entryPoint) {
|
||||||
|
return nativeExecuteEntryPoint(vmPtr, entryPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume execution with an optional JSON value.
|
||||||
|
*
|
||||||
|
* @param resumeJson JSON value to resume with, or null for no value.
|
||||||
|
* @return JSON result string.
|
||||||
|
*/
|
||||||
|
public String resume(String resumeJson) {
|
||||||
|
return nativeResume(vmPtr, resumeJson, resumeJson != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current execution state.
|
||||||
|
*
|
||||||
|
* @return Execution state string.
|
||||||
|
*/
|
||||||
|
public String getExecutionState() {
|
||||||
|
return nativeGetExecutionState(vmPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
nativeDrop(vmPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+30
-10
@@ -52,6 +52,16 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"unty",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -103,9 +113,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.54"
|
version = "1.2.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -191,9 +201,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fluent-uri"
|
name = "fluent-uri"
|
||||||
@@ -263,9 +273,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.64"
|
version = "0.1.65"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -395,6 +405,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -876,10 +888,12 @@ name = "regorus"
|
|||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"jsonschema",
|
"jsonschema",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -1110,6 +1124,12 @@ version = "0.2.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unty"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -1313,18 +1333,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ coverage = ["regorus/coverage"]
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
ordered-float = "5.0.0"
|
ordered-float = "5.0.0"
|
||||||
pyo3 = { version = "0.24.1", features = ["abi3-py310", "anyhow", "extension-module"] }
|
pyo3 = { version = "0.24.1", features = ["abi3-py310", "anyhow", "extension-module"] }
|
||||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
|
|
||||||
|
|||||||
+165
-2
@@ -8,7 +8,14 @@ use pyo3::IntoPyObjectExt;
|
|||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use ::regorus::Value;
|
use ::regorus::languages::rego::compiler::Compiler;
|
||||||
|
use ::regorus::rvm::program::{
|
||||||
|
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
|
||||||
|
DeserializationResult, Program as RvmProgram,
|
||||||
|
};
|
||||||
|
use ::regorus::rvm::vm::{ExecutionMode, RegoVM};
|
||||||
|
use ::regorus::{compile_policy_with_entrypoint, PolicyModule, Rc, Value};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Regorus engine.
|
/// Regorus engine.
|
||||||
#[pyclass(unsendable)]
|
#[pyclass(unsendable)]
|
||||||
@@ -16,6 +23,18 @@ pub struct Engine {
|
|||||||
engine: ::regorus::Engine,
|
engine: ::regorus::Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// RVM program wrapper.
|
||||||
|
#[pyclass(unsendable)]
|
||||||
|
pub struct Program {
|
||||||
|
program: Arc<RvmProgram>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RVM runtime wrapper.
|
||||||
|
#[pyclass(unsendable)]
|
||||||
|
pub struct Rvm {
|
||||||
|
vm: RegoVM,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Engine {
|
impl Default for Engine {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
@@ -384,7 +403,151 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Program {
|
||||||
|
/// Compile an RVM program from modules and entry points.
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn compile_from_modules(
|
||||||
|
data_json: String,
|
||||||
|
modules: Vec<(String, String)>,
|
||||||
|
entry_points: Vec<String>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
if entry_points.is_empty() {
|
||||||
|
return Err(anyhow!("entry_points must contain at least one entry"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = Value::from_json_str(&data_json)?;
|
||||||
|
let policy_modules: Vec<PolicyModule> = modules
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, content)| PolicyModule {
|
||||||
|
id: Rc::from(id.as_str()),
|
||||||
|
content: Rc::from(content.as_str()),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let entry_points_ref: Vec<&str> = entry_points.iter().map(|s| s.as_str()).collect();
|
||||||
|
let entry_rule = Rc::from(entry_points_ref[0]);
|
||||||
|
let compiled = compile_policy_with_entrypoint(data, &policy_modules, entry_rule)?;
|
||||||
|
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
|
||||||
|
Ok(Self { program })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize an RVM program from binary data.
|
||||||
|
#[staticmethod]
|
||||||
|
pub fn deserialize_binary(data: Vec<u8>) -> Result<(Self, bool)> {
|
||||||
|
let (program, is_partial) =
|
||||||
|
match RvmProgram::deserialize_binary(&data).map_err(|e: String| anyhow!(e))? {
|
||||||
|
DeserializationResult::Complete(program) => (program, false),
|
||||||
|
DeserializationResult::Partial(program) => (program, true),
|
||||||
|
};
|
||||||
|
Ok((
|
||||||
|
Self {
|
||||||
|
program: Arc::new(program),
|
||||||
|
},
|
||||||
|
is_partial,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a program to binary format.
|
||||||
|
pub fn serialize_binary(&self) -> Result<Vec<u8>> {
|
||||||
|
self.program
|
||||||
|
.serialize_binary()
|
||||||
|
.map_err(|e: String| anyhow!(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a readable assembly listing.
|
||||||
|
pub fn generate_listing(&self) -> Result<String> {
|
||||||
|
Ok(generate_assembly_listing(
|
||||||
|
self.program.as_ref(),
|
||||||
|
&AssemblyListingConfig::default(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a tabular assembly listing.
|
||||||
|
pub fn generate_tabular_listing(&self) -> Result<String> {
|
||||||
|
Ok(generate_tabular_assembly_listing(
|
||||||
|
self.program.as_ref(),
|
||||||
|
&AssemblyListingConfig::default(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Rvm {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Rvm {
|
||||||
|
#[new]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { vm: RegoVM::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load an RVM program into the VM.
|
||||||
|
pub fn load_program(&mut self, program: &Program) -> Result<()> {
|
||||||
|
self.vm.load_program(program.program.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set data JSON for the VM.
|
||||||
|
pub fn set_data_json(&mut self, data_json: String) -> Result<()> {
|
||||||
|
let data = Value::from_json_str(&data_json)?;
|
||||||
|
self.vm.set_data(data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set input JSON for the VM.
|
||||||
|
pub fn set_input_json(&mut self, input_json: String) -> Result<()> {
|
||||||
|
let input = Value::from_json_str(&input_json)?;
|
||||||
|
self.vm.set_input(input);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set execution mode (0 = run-to-completion, 1 = suspendable).
|
||||||
|
pub fn set_execution_mode(&mut self, mode: u8) -> Result<()> {
|
||||||
|
let mode = match mode {
|
||||||
|
0 => ExecutionMode::RunToCompletion,
|
||||||
|
1 => ExecutionMode::Suspendable,
|
||||||
|
_ => return Err(anyhow!("invalid execution mode")),
|
||||||
|
};
|
||||||
|
self.vm.set_execution_mode(mode);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the program and return the JSON result.
|
||||||
|
pub fn execute(&mut self) -> Result<String> {
|
||||||
|
self.vm.execute()?.to_json_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute an entry point by name and return the JSON result.
|
||||||
|
pub fn execute_entry_point(&mut self, entry_point: String) -> Result<String> {
|
||||||
|
self.vm
|
||||||
|
.execute_entry_point_by_name(&entry_point)?
|
||||||
|
.to_json_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume execution with an optional JSON value.
|
||||||
|
pub fn resume(&mut self, resume_json: Option<String>) -> Result<String> {
|
||||||
|
let value = if let Some(json) = resume_json {
|
||||||
|
Some(Value::from_json_str(&json)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.vm.resume(value)?.to_json_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the execution state as a string.
|
||||||
|
pub fn get_execution_state(&self) -> Result<String> {
|
||||||
|
Ok(format!("{:?}", self.vm.execution_state()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
pub fn regorus(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
pub fn regorus(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
m.add_class::<crate::Engine>()
|
m.add_class::<crate::Engine>()?;
|
||||||
|
m.add_class::<crate::Program>()?;
|
||||||
|
m.add_class::<crate::Rvm>()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
import regorus
|
import regorus
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if hasattr(sys.stdout, "reconfigure"):
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
# Create engine
|
# Create engine
|
||||||
engine = regorus.Engine()
|
engine = regorus.Engine()
|
||||||
@@ -94,3 +98,68 @@ engine1.set_gather_prints(True)
|
|||||||
engine1.eval_query('print("Hello")')
|
engine1.eval_query('print("Hello")')
|
||||||
ps = engine1.take_prints()
|
ps = engine1.take_prints()
|
||||||
print(ps)
|
print(ps)
|
||||||
|
|
||||||
|
# RVM regular example
|
||||||
|
policy = """
|
||||||
|
package demo
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
default allow := false
|
||||||
|
|
||||||
|
allow if {
|
||||||
|
input.user == "alice"
|
||||||
|
input.active == true
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
def run_regular_example():
|
||||||
|
module = ("demo.rego", policy)
|
||||||
|
program = regorus.Program.compile_from_modules(
|
||||||
|
"{}",
|
||||||
|
[module],
|
||||||
|
["data.demo.allow"],
|
||||||
|
)
|
||||||
|
|
||||||
|
print(program.generate_listing())
|
||||||
|
|
||||||
|
binary = program.serialize_binary()
|
||||||
|
program, is_partial = regorus.Program.deserialize_binary(binary)
|
||||||
|
if is_partial:
|
||||||
|
raise RuntimeError("Deserialized program marked partial")
|
||||||
|
|
||||||
|
vm = regorus.Rvm()
|
||||||
|
vm.load_program(program)
|
||||||
|
vm.set_input_json('{"user":"alice","active":true}')
|
||||||
|
print(vm.execute())
|
||||||
|
|
||||||
|
run_regular_example()
|
||||||
|
|
||||||
|
# RVM HostAwait example
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
def run_host_await_example():
|
||||||
|
module = ("await.rego", policy)
|
||||||
|
program = regorus.Program.compile_from_modules(
|
||||||
|
"{}",
|
||||||
|
[module],
|
||||||
|
["data.demo.allow"],
|
||||||
|
)
|
||||||
|
|
||||||
|
vm = regorus.Rvm()
|
||||||
|
vm.set_execution_mode(1)
|
||||||
|
vm.load_program(program)
|
||||||
|
vm.set_input_json('{"account":{"id":"acct-1","active":true}}')
|
||||||
|
vm.execute()
|
||||||
|
print(vm.get_execution_state())
|
||||||
|
print(vm.resume('{"tier":"gold"}'))
|
||||||
|
|
||||||
|
run_host_await_example()
|
||||||
|
|||||||
Generated
+31
-10
@@ -63,6 +63,16 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"unty",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -120,9 +130,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.54"
|
version = "1.2.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -208,9 +218,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fluent-uri"
|
name = "fluent-uri"
|
||||||
@@ -314,9 +324,9 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.64"
|
version = "0.1.65"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -446,6 +456,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -874,10 +886,12 @@ name = "regorus"
|
|||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"jsonschema",
|
"jsonschema",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -903,6 +917,7 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"regorus",
|
"regorus",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -1098,6 +1113,12 @@ version = "0.2.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unty"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -1398,18 +1419,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
|
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.34"
|
version = "0.8.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
|
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ ast = ["regorus/ast"]
|
|||||||
coverage = ["regorus/coverage"]
|
coverage = ["regorus/coverage"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
wasm-bindgen = "0.2.100"
|
wasm-bindgen = "0.2.100"
|
||||||
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
|
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
|
||||||
|
|||||||
@@ -3,6 +3,15 @@
|
|||||||
|
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use regorus::languages::rego::compiler::Compiler;
|
||||||
|
use regorus::rvm::program::{
|
||||||
|
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
|
||||||
|
DeserializationResult, Program as RvmProgram,
|
||||||
|
};
|
||||||
|
use regorus::rvm::vm::{ExecutionMode, RegoVM};
|
||||||
|
use regorus::{compile_policy_with_entrypoint, PolicyModule, Rc, Value};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::Arc;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -11,6 +20,44 @@ pub struct Engine {
|
|||||||
engine: regorus::Engine,
|
engine: regorus::Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ModuleSpec {
|
||||||
|
id: String,
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Program {
|
||||||
|
program: Arc<RvmProgram>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct ProgramDeserializationResult {
|
||||||
|
program: Arc<RvmProgram>,
|
||||||
|
is_partial: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl ProgramDeserializationResult {
|
||||||
|
/// Whether the program was partially deserialized.
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn isPartial(&self) -> bool {
|
||||||
|
self.is_partial
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the deserialized program.
|
||||||
|
pub fn program(&self) -> Program {
|
||||||
|
Program {
|
||||||
|
program: self.program.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Rvm {
|
||||||
|
vm: RegoVM,
|
||||||
|
}
|
||||||
|
|
||||||
fn error_to_jsvalue<E: std::fmt::Display>(e: E) -> JsValue {
|
fn error_to_jsvalue<E: std::fmt::Display>(e: E) -> JsValue {
|
||||||
JsValue::from_str(&format!("{e}"))
|
JsValue::from_str(&format!("{e}"))
|
||||||
}
|
}
|
||||||
@@ -193,6 +240,153 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Program {
|
||||||
|
/// Compile an RVM program from modules and entry points.
|
||||||
|
pub fn compileFromModules(
|
||||||
|
data_json: String,
|
||||||
|
modules_json: String,
|
||||||
|
entry_points_json: String,
|
||||||
|
) -> Result<Program, JsValue> {
|
||||||
|
let data = Value::from_json_str(&data_json).map_err(error_to_jsvalue)?;
|
||||||
|
let modules: Vec<ModuleSpec> =
|
||||||
|
serde_json::from_str(&modules_json).map_err(error_to_jsvalue)?;
|
||||||
|
let entry_points: Vec<String> =
|
||||||
|
serde_json::from_str(&entry_points_json).map_err(error_to_jsvalue)?;
|
||||||
|
if entry_points.is_empty() {
|
||||||
|
return Err(error_to_jsvalue(
|
||||||
|
"entry_points must contain at least one entry",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let policy_modules: Vec<PolicyModule> = modules
|
||||||
|
.into_iter()
|
||||||
|
.map(|module| PolicyModule {
|
||||||
|
id: Rc::from(module.id.as_str()),
|
||||||
|
content: Rc::from(module.content.as_str()),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let entry_points_ref: Vec<&str> = entry_points.iter().map(|s| s.as_str()).collect();
|
||||||
|
let compiled =
|
||||||
|
compile_policy_with_entrypoint(data, &policy_modules, Rc::from(entry_points_ref[0]))
|
||||||
|
.map_err(error_to_jsvalue)?;
|
||||||
|
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)
|
||||||
|
.map_err(error_to_jsvalue)?;
|
||||||
|
Ok(Program { program })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a program to binary format.
|
||||||
|
pub fn serializeBinary(&self) -> Result<Vec<u8>, JsValue> {
|
||||||
|
self.program
|
||||||
|
.serialize_binary()
|
||||||
|
.map_err(|e| error_to_jsvalue(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize an RVM program from binary format.
|
||||||
|
pub fn deserializeBinary(data: Vec<u8>) -> Result<ProgramDeserializationResult, JsValue> {
|
||||||
|
let (program, is_partial) =
|
||||||
|
match RvmProgram::deserialize_binary(&data).map_err(error_to_jsvalue)? {
|
||||||
|
DeserializationResult::Complete(program) => (program, false),
|
||||||
|
DeserializationResult::Partial(program) => (program, true),
|
||||||
|
};
|
||||||
|
Ok(ProgramDeserializationResult {
|
||||||
|
program: Arc::new(program),
|
||||||
|
is_partial,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a readable assembly listing.
|
||||||
|
pub fn generateListing(&self) -> Result<String, JsValue> {
|
||||||
|
Ok(generate_assembly_listing(
|
||||||
|
self.program.as_ref(),
|
||||||
|
&AssemblyListingConfig::default(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a tabular assembly listing.
|
||||||
|
pub fn generateTabularListing(&self) -> Result<String, JsValue> {
|
||||||
|
Ok(generate_tabular_assembly_listing(
|
||||||
|
self.program.as_ref(),
|
||||||
|
&AssemblyListingConfig::default(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Rvm {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { vm: RegoVM::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a program into the VM.
|
||||||
|
pub fn loadProgram(&mut self, program: &Program) {
|
||||||
|
self.vm.load_program(program.program.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set VM data from JSON.
|
||||||
|
pub fn setDataJson(&mut self, data_json: String) -> Result<(), JsValue> {
|
||||||
|
let data = Value::from_json_str(&data_json).map_err(error_to_jsvalue)?;
|
||||||
|
self.vm.set_data(data).map_err(error_to_jsvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set VM input from JSON.
|
||||||
|
pub fn setInputJson(&mut self, input_json: String) -> Result<(), JsValue> {
|
||||||
|
let input = Value::from_json_str(&input_json).map_err(error_to_jsvalue)?;
|
||||||
|
self.vm.set_input(input);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set execution mode (0 = run-to-completion, 1 = suspendable).
|
||||||
|
pub fn setExecutionMode(&mut self, mode: u8) -> Result<(), JsValue> {
|
||||||
|
let mode = match mode {
|
||||||
|
0 => ExecutionMode::RunToCompletion,
|
||||||
|
1 => ExecutionMode::Suspendable,
|
||||||
|
_ => return Err(error_to_jsvalue("invalid execution mode")),
|
||||||
|
};
|
||||||
|
self.vm.set_execution_mode(mode);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the program and return the JSON result.
|
||||||
|
pub fn execute(&mut self) -> Result<String, JsValue> {
|
||||||
|
let value = self.vm.execute().map_err(error_to_jsvalue)?;
|
||||||
|
value.to_json_str().map_err(error_to_jsvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute an entry point by name and return the JSON result.
|
||||||
|
pub fn executeEntryPoint(&mut self, entry_point: String) -> Result<String, JsValue> {
|
||||||
|
let value = self
|
||||||
|
.vm
|
||||||
|
.execute_entry_point_by_name(&entry_point)
|
||||||
|
.map_err(error_to_jsvalue)?;
|
||||||
|
value.to_json_str().map_err(error_to_jsvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume execution with an optional JSON value.
|
||||||
|
pub fn resume(&mut self, resume_json: Option<String>) -> Result<String, JsValue> {
|
||||||
|
let value = if let Some(json) = resume_json {
|
||||||
|
Some(Value::from_json_str(&json).map_err(error_to_jsvalue)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let result = self.vm.resume(value).map_err(error_to_jsvalue)?;
|
||||||
|
result.to_json_str().map_err(error_to_jsvalue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the execution state as a string.
|
||||||
|
pub fn getExecutionState(&self) -> String {
|
||||||
|
format!("{:?}", self.vm.execution_state())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Rvm {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::error_to_jsvalue;
|
use crate::error_to_jsvalue;
|
||||||
|
|||||||
@@ -85,3 +85,78 @@ console.log(report);
|
|||||||
// Print pretty report.
|
// Print pretty report.
|
||||||
report = engine.getCoverageReportPretty();
|
report = engine.getCoverageReportPretty();
|
||||||
console.log(report);
|
console.log(report);
|
||||||
|
|
||||||
|
// RVM regular example
|
||||||
|
{
|
||||||
|
const policy = `
|
||||||
|
package demo
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
default allow := false
|
||||||
|
|
||||||
|
allow if {
|
||||||
|
input.user == "alice"
|
||||||
|
input.active == true
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const modules = JSON.stringify([
|
||||||
|
{ id: "demo.rego", content: policy }
|
||||||
|
]);
|
||||||
|
const entryPoints = JSON.stringify(["data.demo.allow"]);
|
||||||
|
|
||||||
|
const program = regorus.Program.compileFromModules(
|
||||||
|
"{}",
|
||||||
|
modules,
|
||||||
|
entryPoints
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(program.generateListing());
|
||||||
|
|
||||||
|
const binary = program.serializeBinary();
|
||||||
|
const deserialized = regorus.Program.deserializeBinary(binary);
|
||||||
|
if (deserialized.isPartial) {
|
||||||
|
throw new Error("Deserialized program marked partial");
|
||||||
|
}
|
||||||
|
const rehydrated = deserialized.program();
|
||||||
|
|
||||||
|
const vm = new regorus.Rvm();
|
||||||
|
vm.loadProgram(rehydrated);
|
||||||
|
vm.setInputJson('{"user":"alice","active":true}');
|
||||||
|
console.log(vm.execute());
|
||||||
|
}
|
||||||
|
|
||||||
|
// RVM HostAwait example
|
||||||
|
{
|
||||||
|
const 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"
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const modules = JSON.stringify([
|
||||||
|
{ id: "await.rego", content: policy }
|
||||||
|
]);
|
||||||
|
const entryPoints = JSON.stringify(["data.demo.allow"]);
|
||||||
|
|
||||||
|
const program = regorus.Program.compileFromModules(
|
||||||
|
"{}",
|
||||||
|
modules,
|
||||||
|
entryPoints
|
||||||
|
);
|
||||||
|
|
||||||
|
const vm = new regorus.Rvm();
|
||||||
|
vm.setExecutionMode(1);
|
||||||
|
vm.loadProgram(program);
|
||||||
|
vm.setInputJson('{"account":{"id":"acct-1","active":true}}');
|
||||||
|
vm.execute();
|
||||||
|
console.log(vm.getExecutionState());
|
||||||
|
console.log(vm.resume('{"tier":"gold"}'));
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ enum CallTarget {
|
|||||||
builtin_index: u16,
|
builtin_index: u16,
|
||||||
expected_args: Option<usize>,
|
expected_args: Option<usize>,
|
||||||
},
|
},
|
||||||
|
HostAwait {
|
||||||
|
expected_args: Option<usize>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Compiler<'a> {
|
impl<'a> Compiler<'a> {
|
||||||
@@ -56,6 +59,7 @@ impl<'a> Compiler<'a> {
|
|||||||
let expected_args = match &call_target {
|
let expected_args = match &call_target {
|
||||||
CallTarget::User { expected_args, .. } => *expected_args,
|
CallTarget::User { expected_args, .. } => *expected_args,
|
||||||
CallTarget::Builtin { expected_args, .. } => *expected_args,
|
CallTarget::Builtin { expected_args, .. } => *expected_args,
|
||||||
|
CallTarget::HostAwait { expected_args } => *expected_args,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(expected) = expected_args {
|
if let Some(expected) = expected_args {
|
||||||
@@ -122,6 +126,26 @@ impl<'a> Compiler<'a> {
|
|||||||
});
|
});
|
||||||
self.emit_instruction(Instruction::BuiltinCall { params_index }, &span);
|
self.emit_instruction(Instruction::BuiltinCall { params_index }, &span);
|
||||||
}
|
}
|
||||||
|
CallTarget::HostAwait { .. } => {
|
||||||
|
if arg_regs.len() != 2 {
|
||||||
|
return Err(CompilerError::General {
|
||||||
|
message: format!(
|
||||||
|
"__builtin_host_await expects 2 arguments, got {}",
|
||||||
|
arg_regs.len()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.at(&span));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit_instruction(
|
||||||
|
Instruction::HostAwait {
|
||||||
|
dest,
|
||||||
|
arg: arg_regs[0],
|
||||||
|
id: arg_regs[1],
|
||||||
|
},
|
||||||
|
&span,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((plan, plan_span)) = &out_param_plan {
|
if let Some((plan, plan_span)) = &out_param_plan {
|
||||||
@@ -162,6 +186,12 @@ impl<'a> Compiler<'a> {
|
|||||||
full_fcn_path: &str,
|
full_fcn_path: &str,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
) -> Result<CallTarget> {
|
) -> Result<CallTarget> {
|
||||||
|
if original_fcn_path == "__builtin_host_await" {
|
||||||
|
return Ok(CallTarget::HostAwait {
|
||||||
|
expected_args: Some(2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if self.is_user_defined_function(full_fcn_path) {
|
if self.is_user_defined_function(full_fcn_path) {
|
||||||
let rule_index = self.get_or_assign_rule_index(full_fcn_path)?;
|
let rule_index = self.get_or_assign_rule_index(full_fcn_path)?;
|
||||||
let expected_args = self
|
let expected_args = self
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
cases:
|
||||||
|
- note: host_await_run_to_completion
|
||||||
|
data: {}
|
||||||
|
input:
|
||||||
|
enabled: true
|
||||||
|
skip_interpreter: true
|
||||||
|
execution_mode: run-to-completion
|
||||||
|
modules:
|
||||||
|
- |
|
||||||
|
package demo
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
allow := [
|
||||||
|
__builtin_host_await("ping", "id-1"),
|
||||||
|
__builtin_host_await("pong", "id-2")
|
||||||
|
] if {
|
||||||
|
input.enabled
|
||||||
|
}
|
||||||
|
query: data.demo.allow
|
||||||
|
host_await_responses:
|
||||||
|
- id: "id-1"
|
||||||
|
value: "response-1"
|
||||||
|
- id: "id-2"
|
||||||
|
value: "response-2"
|
||||||
|
want_result: ["response-1", "response-2"]
|
||||||
|
|
||||||
|
- note: host_await_suspendable_queue
|
||||||
|
data: {}
|
||||||
|
input:
|
||||||
|
enabled: true
|
||||||
|
payloads: ["a", "b"]
|
||||||
|
skip_interpreter: true
|
||||||
|
execution_mode: suspendable
|
||||||
|
modules:
|
||||||
|
- |
|
||||||
|
package demo
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
allow := [result |
|
||||||
|
input.enabled
|
||||||
|
payload := input.payloads[_]
|
||||||
|
result := __builtin_host_await(payload, "queue")
|
||||||
|
]
|
||||||
|
query: data.demo.allow
|
||||||
|
host_await_responses_suspendable:
|
||||||
|
- id: "queue"
|
||||||
|
value: "first"
|
||||||
|
- id: "queue"
|
||||||
|
value: "second"
|
||||||
|
want_result: ["first", "second"]
|
||||||
|
|
||||||
|
- note: host_await_nested_rule
|
||||||
|
data: {}
|
||||||
|
input:
|
||||||
|
enabled: true
|
||||||
|
token: "alpha"
|
||||||
|
skip_interpreter: true
|
||||||
|
execution_mode: suspendable
|
||||||
|
modules:
|
||||||
|
- |
|
||||||
|
package demo
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
allow if {
|
||||||
|
input.enabled
|
||||||
|
result := nested_result
|
||||||
|
result == "ok"
|
||||||
|
}
|
||||||
|
|
||||||
|
nested_result := __builtin_host_await(input.token, "nested")
|
||||||
|
query: data.demo.allow
|
||||||
|
host_await_responses_suspendable:
|
||||||
|
- id: "nested"
|
||||||
|
value: "ok"
|
||||||
|
want_result: true
|
||||||
|
|
||||||
|
- note: host_await_nested_comprehension
|
||||||
|
data: {}
|
||||||
|
input:
|
||||||
|
enabled: true
|
||||||
|
items: ["x", "y"]
|
||||||
|
skip_interpreter: true
|
||||||
|
execution_mode: suspendable
|
||||||
|
modules:
|
||||||
|
- |
|
||||||
|
package demo
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
allow := [outer |
|
||||||
|
input.enabled
|
||||||
|
inner := [r |
|
||||||
|
item := input.items[_]
|
||||||
|
r := __builtin_host_await(item, "nested-comp")
|
||||||
|
]
|
||||||
|
outer := inner
|
||||||
|
]
|
||||||
|
query: data.demo.allow
|
||||||
|
host_await_responses_suspendable:
|
||||||
|
- id: "nested-comp"
|
||||||
|
value: "first"
|
||||||
|
- id: "nested-comp"
|
||||||
|
value: "second"
|
||||||
|
want_result: [["first", "second"]]
|
||||||
+206
-28
@@ -6,10 +6,11 @@ use anyhow::Result;
|
|||||||
use regorus::languages::rego::compiler::Compiler;
|
use regorus::languages::rego::compiler::Compiler;
|
||||||
use regorus::rvm::program::{generate_tabular_assembly_listing, AssemblyListingConfig, Program};
|
use regorus::rvm::program::{generate_tabular_assembly_listing, AssemblyListingConfig, Program};
|
||||||
use regorus::rvm::tests::test_utils::test_round_trip_serialization;
|
use regorus::rvm::tests::test_utils::test_round_trip_serialization;
|
||||||
use regorus::rvm::vm::RegoVM;
|
use regorus::rvm::vm::{ExecutionMode, ExecutionState, RegoVM, SuspendReason};
|
||||||
use regorus::test_utils::{check_output, process_value, value_or_vec_to_vec, ValueOrVec};
|
use regorus::test_utils::{check_output, process_value, value_or_vec_to_vec, ValueOrVec};
|
||||||
use regorus::{CompiledPolicy, Engine, Rc, Value};
|
use regorus::{CompiledPolicy, Engine, Rc, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use test_generator::test_resources;
|
use test_generator::test_resources;
|
||||||
|
|
||||||
@@ -35,6 +36,11 @@ struct TestCase {
|
|||||||
pub strict: bool,
|
pub strict: bool,
|
||||||
pub allow_interpreter_success: Option<bool>,
|
pub allow_interpreter_success: Option<bool>,
|
||||||
pub allow_interpreter_incorrect_behavior: Option<bool>,
|
pub allow_interpreter_incorrect_behavior: Option<bool>,
|
||||||
|
pub skip_interpreter: Option<bool>,
|
||||||
|
pub execution_mode: Option<String>,
|
||||||
|
pub host_await_responses: Option<Vec<HostAwaitResponseSpec>>,
|
||||||
|
pub host_await_responses_run_to_completion: Option<Vec<HostAwaitResponseSpec>>,
|
||||||
|
pub host_await_responses_suspendable: Option<Vec<HostAwaitResponseSpec>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_strict() -> bool {
|
fn default_strict() -> bool {
|
||||||
@@ -46,11 +52,86 @@ struct YamlTest {
|
|||||||
pub cases: Vec<TestCase>,
|
pub cases: Vec<TestCase>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
|
struct HostAwaitResponseSpec {
|
||||||
|
pub id: Value,
|
||||||
|
pub value: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct RvmExecutionOptions {
|
||||||
|
execution_mode: ExecutionMode,
|
||||||
|
host_await_responses_run_to_completion: Option<Vec<(Value, Vec<Value>)>>,
|
||||||
|
host_await_responses_suspendable: Option<BTreeMap<Value, VecDeque<Value>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RvmExecutionOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
execution_mode: ExecutionMode::RunToCompletion,
|
||||||
|
host_await_responses_run_to_completion: None,
|
||||||
|
host_await_responses_suspendable: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_program_listing(program: &Program) -> String {
|
fn render_program_listing(program: &Program) -> String {
|
||||||
let config = AssemblyListingConfig::default();
|
let config = AssemblyListingConfig::default();
|
||||||
generate_tabular_assembly_listing(program, &config)
|
generate_tabular_assembly_listing(program, &config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_host_await_response_map(
|
||||||
|
responses: &[HostAwaitResponseSpec],
|
||||||
|
) -> anyhow::Result<BTreeMap<Value, VecDeque<Value>>> {
|
||||||
|
let mut map: BTreeMap<Value, VecDeque<Value>> = BTreeMap::new();
|
||||||
|
for response in responses {
|
||||||
|
let id = process_value(&response.id)?;
|
||||||
|
let value = process_value(&response.value)?;
|
||||||
|
map.entry(id).or_default().push_back(value);
|
||||||
|
}
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_host_await_response_vec(
|
||||||
|
responses: &[HostAwaitResponseSpec],
|
||||||
|
) -> anyhow::Result<Vec<(Value, Vec<Value>)>> {
|
||||||
|
let map = build_host_await_response_map(responses)?;
|
||||||
|
Ok(map
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, values)| (id, values.into_iter().collect()))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_execution_options(case: &TestCase) -> anyhow::Result<RvmExecutionOptions> {
|
||||||
|
let execution_mode = match case.execution_mode.as_deref() {
|
||||||
|
None | Some("run-to-completion") => ExecutionMode::RunToCompletion,
|
||||||
|
Some("suspendable") => ExecutionMode::Suspendable,
|
||||||
|
Some(other) => {
|
||||||
|
return Err(anyhow::anyhow!("unsupported execution_mode: {other}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rtc_responses = case
|
||||||
|
.host_await_responses_run_to_completion
|
||||||
|
.as_ref()
|
||||||
|
.or(case.host_await_responses.as_ref())
|
||||||
|
.map(|responses| build_host_await_response_vec(responses))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let suspendable_responses = case
|
||||||
|
.host_await_responses_suspendable
|
||||||
|
.as_ref()
|
||||||
|
.or(case.host_await_responses.as_ref())
|
||||||
|
.map(|responses| build_host_await_response_map(responses))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
Ok(RvmExecutionOptions {
|
||||||
|
execution_mode,
|
||||||
|
host_await_responses_run_to_completion: rtc_responses,
|
||||||
|
host_await_responses_suspendable: suspendable_responses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn dump_rvm_listing(case_note: &str, listing: &Option<String>) {
|
fn dump_rvm_listing(case_note: &str, listing: &Option<String>) {
|
||||||
if let Some(listing) = listing {
|
if let Some(listing) = listing {
|
||||||
eprintln!("\n===== RVM assembly for '{}' =====", case_note);
|
eprintln!("\n===== RVM assembly for '{}' =====", case_note);
|
||||||
@@ -87,6 +168,7 @@ fn compile_and_run_rvm(
|
|||||||
data: &Value,
|
data: &Value,
|
||||||
input: &Value,
|
input: &Value,
|
||||||
listing_out: &mut Option<String>,
|
listing_out: &mut Option<String>,
|
||||||
|
execution_options: &RvmExecutionOptions,
|
||||||
) -> anyhow::Result<Value> {
|
) -> anyhow::Result<Value> {
|
||||||
let results = compile_and_run_rvm_with_all_entry_points(
|
let results = compile_and_run_rvm_with_all_entry_points(
|
||||||
compiled_policy,
|
compiled_policy,
|
||||||
@@ -94,6 +176,7 @@ fn compile_and_run_rvm(
|
|||||||
data,
|
data,
|
||||||
input,
|
input,
|
||||||
listing_out,
|
listing_out,
|
||||||
|
execution_options,
|
||||||
)?;
|
)?;
|
||||||
results
|
results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -108,6 +191,7 @@ fn compile_and_run_rvm_with_entry_points(
|
|||||||
data: &Value,
|
data: &Value,
|
||||||
input: &Value,
|
input: &Value,
|
||||||
listing_out: &mut Option<String>,
|
listing_out: &mut Option<String>,
|
||||||
|
execution_options: &RvmExecutionOptions,
|
||||||
) -> anyhow::Result<Value> {
|
) -> anyhow::Result<Value> {
|
||||||
let results = compile_and_run_rvm_with_all_entry_points(
|
let results = compile_and_run_rvm_with_all_entry_points(
|
||||||
compiled_policy,
|
compiled_policy,
|
||||||
@@ -115,6 +199,7 @@ fn compile_and_run_rvm_with_entry_points(
|
|||||||
data,
|
data,
|
||||||
input,
|
input,
|
||||||
listing_out,
|
listing_out,
|
||||||
|
execution_options,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(index) = entry_points
|
if let Some(index) = entry_points
|
||||||
@@ -140,6 +225,7 @@ fn compile_and_run_rvm_with_all_entry_points(
|
|||||||
data: &Value,
|
data: &Value,
|
||||||
input: &Value,
|
input: &Value,
|
||||||
listing_out: &mut Option<String>,
|
listing_out: &mut Option<String>,
|
||||||
|
execution_options: &RvmExecutionOptions,
|
||||||
) -> anyhow::Result<Vec<Value>> {
|
) -> anyhow::Result<Vec<Value>> {
|
||||||
let program = Compiler::compile_from_policy(compiled_policy, entry_points)?;
|
let program = Compiler::compile_from_policy(compiled_policy, entry_points)?;
|
||||||
|
|
||||||
@@ -153,9 +239,61 @@ fn compile_and_run_rvm_with_all_entry_points(
|
|||||||
vm.set_data(data.clone())?;
|
vm.set_data(data.clone())?;
|
||||||
vm.set_input(input.clone());
|
vm.set_input(input.clone());
|
||||||
|
|
||||||
|
if execution_options.execution_mode == ExecutionMode::Suspendable {
|
||||||
|
vm.set_execution_mode(ExecutionMode::Suspendable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if execution_options.execution_mode == ExecutionMode::RunToCompletion {
|
||||||
|
if let Some(responses) = &execution_options.host_await_responses_run_to_completion {
|
||||||
|
vm.set_host_await_responses(responses.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for (idx, _) in entry_points.iter().enumerate() {
|
for (idx, _) in entry_points.iter().enumerate() {
|
||||||
let result = if entry_points.len() == 1 {
|
let result = if execution_options.execution_mode == ExecutionMode::Suspendable {
|
||||||
|
let mut suspendable_responses = execution_options
|
||||||
|
.host_await_responses_suspendable
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let _ = if entry_points.len() == 1 {
|
||||||
|
vm.execute()?
|
||||||
|
} else {
|
||||||
|
vm.execute_entry_point_by_index(idx)?
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match vm.execution_state() {
|
||||||
|
ExecutionState::Completed { result } => break result.clone(),
|
||||||
|
ExecutionState::Error { error } => {
|
||||||
|
return Err(anyhow::anyhow!("{}", error));
|
||||||
|
}
|
||||||
|
ExecutionState::Suspended { reason, .. } => match reason {
|
||||||
|
SuspendReason::HostAwait { identifier, .. } => {
|
||||||
|
let response = suspendable_responses
|
||||||
|
.get_mut(identifier)
|
||||||
|
.and_then(|queue| queue.pop_front())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Missing HostAwait response for identifier {:?}",
|
||||||
|
identifier
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
vm.resume(Some(response))?;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Unexpected suspension reason: {:?}",
|
||||||
|
other
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ExecutionState::Running | ExecutionState::Ready => {
|
||||||
|
return Err(anyhow::anyhow!("VM stuck in running state"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if entry_points.len() == 1 {
|
||||||
vm.execute()?
|
vm.execute()?
|
||||||
} else {
|
} else {
|
||||||
vm.execute_entry_point_by_index(idx)?
|
vm.execute_entry_point_by_index(idx)?
|
||||||
@@ -201,8 +339,8 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
engine.add_policy(format!("rego_{idx}"), module.clone())?;
|
engine.add_policy(format!("rego_{idx}"), module.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(data) = case.data {
|
if let Some(ref data) = case.data {
|
||||||
engine.add_data(data)?;
|
engine.add_data(data.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let input_value = case
|
let input_value = case
|
||||||
@@ -221,7 +359,13 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
let entrypoint_ref = Rc::from(case.query.as_str());
|
let entrypoint_ref = Rc::from(case.query.as_str());
|
||||||
let compilation_result = engine.compile_with_entrypoint(&entrypoint_ref);
|
let compilation_result = engine.compile_with_entrypoint(&entrypoint_ref);
|
||||||
let data = engine.get_data();
|
let data = engine.get_data();
|
||||||
let interpreter_result = engine.eval_rule(case.query.clone());
|
let interpreter_result = if case.skip_interpreter == Some(true) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(engine.eval_rule(case.query.clone()))
|
||||||
|
};
|
||||||
|
|
||||||
|
let execution_options = build_execution_options(&case)?;
|
||||||
|
|
||||||
if let Err(compilation_error) = &compilation_result {
|
if let Err(compilation_error) = &compilation_result {
|
||||||
if let (None, Some(expected_error)) = (&case.want_result, &case.want_error) {
|
if let (None, Some(expected_error)) = (&case.want_result, &case.want_error) {
|
||||||
@@ -275,6 +419,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
&data,
|
&data,
|
||||||
&input_value,
|
&input_value,
|
||||||
&mut last_listing,
|
&mut last_listing,
|
||||||
|
&execution_options,
|
||||||
) {
|
) {
|
||||||
Ok(actual_results) => {
|
Ok(actual_results) => {
|
||||||
if actual_results.len() != expected_results.len() {
|
if actual_results.len() != expected_results.len() {
|
||||||
@@ -359,6 +504,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
&data,
|
&data,
|
||||||
&input_value,
|
&input_value,
|
||||||
&mut last_listing,
|
&mut last_listing,
|
||||||
|
&execution_options,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
compile_and_run_rvm(
|
compile_and_run_rvm(
|
||||||
@@ -367,41 +513,52 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
&data,
|
&data,
|
||||||
&input_value,
|
&input_value,
|
||||||
&mut last_listing,
|
&mut last_listing,
|
||||||
|
&execution_options,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(actual_result) => {
|
Ok(actual_result) => {
|
||||||
match &interpreter_result {
|
if let Some(interpreter_result) = &interpreter_result {
|
||||||
Ok(interpreter_value) => {
|
match interpreter_result {
|
||||||
if actual_result != *interpreter_value {
|
Ok(interpreter_value) => {
|
||||||
|
if actual_result != *interpreter_value {
|
||||||
|
if case.allow_interpreter_incorrect_behavior == Some(true) {
|
||||||
|
println!(
|
||||||
|
"✓ RVM result differs from interpreter for case '{}' (allowed)",
|
||||||
|
case.note
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic_with_listing!(
|
||||||
|
&last_listing,
|
||||||
|
&case.note,
|
||||||
|
"RVM result does not match interpreter result for case '{}':\nRVM: {:?}\nInterpreter: {:?}",
|
||||||
|
case.note,
|
||||||
|
actual_result,
|
||||||
|
interpreter_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
if case.allow_interpreter_incorrect_behavior == Some(true) {
|
if case.allow_interpreter_incorrect_behavior == Some(true) {
|
||||||
println!(
|
println!(
|
||||||
"✓ RVM result differs from interpreter for case '{}' (allowed)",
|
"✓ Interpreter failed for case '{}' but RVM succeeded (allowed): {}",
|
||||||
case.note
|
case.note,
|
||||||
|
err
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
panic_with_listing!(
|
panic_with_listing!(
|
||||||
&last_listing,
|
&last_listing,
|
||||||
&case.note,
|
&case.note,
|
||||||
"RVM result does not match interpreter result for case '{}':\nRVM: {:?}\nInterpreter: {:?}",
|
"Interpreter failed for case '{}' but RVM succeeded:\nRVM result: {:?}\nInterpreter error: {}",
|
||||||
case.note,
|
case.note,
|
||||||
actual_result,
|
actual_result,
|
||||||
interpreter_value
|
err
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
|
||||||
panic_with_listing!(
|
|
||||||
&last_listing,
|
|
||||||
&case.note,
|
|
||||||
"Interpreter failed for case '{}' but RVM succeeded:\nRVM result: {:?}\nInterpreter error: {}",
|
|
||||||
case.note,
|
|
||||||
actual_result,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected_results = value_or_vec_to_vec(expected_result.clone());
|
let expected_results = value_or_vec_to_vec(expected_result.clone());
|
||||||
@@ -409,7 +566,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
check_output(&actual_results, &expected_results)?;
|
check_output(&actual_results, &expected_results)?;
|
||||||
}
|
}
|
||||||
Err(e) => match &interpreter_result {
|
Err(e) => match &interpreter_result {
|
||||||
Ok(interpreter_value) => {
|
Some(Ok(interpreter_value)) => {
|
||||||
if case.allow_interpreter_success == Some(true) {
|
if case.allow_interpreter_success == Some(true) {
|
||||||
println!(
|
println!(
|
||||||
"✓ RVM detected conflict for case '{}' (interpreter success allowed): {}",
|
"✓ RVM detected conflict for case '{}' (interpreter success allowed): {}",
|
||||||
@@ -427,7 +584,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Some(Err(err)) => {
|
||||||
panic_with_listing!(
|
panic_with_listing!(
|
||||||
&last_listing,
|
&last_listing,
|
||||||
&case.note,
|
&case.note,
|
||||||
@@ -437,6 +594,15 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
panic_with_listing!(
|
||||||
|
&last_listing,
|
||||||
|
&case.note,
|
||||||
|
"RVM failed for case '{}' but a result was expected:\nRVM error: {}",
|
||||||
|
case.note,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -450,6 +616,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
&data,
|
&data,
|
||||||
&input_value,
|
&input_value,
|
||||||
&mut last_listing,
|
&mut last_listing,
|
||||||
|
&execution_options,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
compile_and_run_rvm(
|
compile_and_run_rvm(
|
||||||
@@ -458,12 +625,13 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
&data,
|
&data,
|
||||||
&input_value,
|
&input_value,
|
||||||
&mut last_listing,
|
&mut last_listing,
|
||||||
|
&execution_options,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(result) => match &interpreter_result {
|
Ok(result) => match &interpreter_result {
|
||||||
Ok(interpreter_value) => {
|
Some(Ok(interpreter_value)) => {
|
||||||
panic_with_listing!(
|
panic_with_listing!(
|
||||||
&last_listing,
|
&last_listing,
|
||||||
&case.note,
|
&case.note,
|
||||||
@@ -474,7 +642,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
interpreter_value
|
interpreter_value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Some(Err(_)) => {
|
||||||
panic_with_listing!(
|
panic_with_listing!(
|
||||||
&last_listing,
|
&last_listing,
|
||||||
&case.note,
|
&case.note,
|
||||||
@@ -484,9 +652,19 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
serde_json::to_string_pretty(&result)?
|
serde_json::to_string_pretty(&result)?
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
panic_with_listing!(
|
||||||
|
&last_listing,
|
||||||
|
&case.note,
|
||||||
|
"Test case '{}' expected error '{}' but RVM succeeded:\nRVM result: {}",
|
||||||
|
case.note,
|
||||||
|
expected_error,
|
||||||
|
serde_json::to_string_pretty(&result)?
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(actual_error) => match &interpreter_result {
|
Err(actual_error) => match &interpreter_result {
|
||||||
Ok(interpreter_value) => {
|
Some(Ok(interpreter_value)) => {
|
||||||
if case.allow_interpreter_success == Some(true) {
|
if case.allow_interpreter_success == Some(true) {
|
||||||
let actual_error_str = actual_error.to_string();
|
let actual_error_str = actual_error.to_string();
|
||||||
if !actual_error_str.contains(expected_error) {
|
if !actual_error_str.contains(expected_error) {
|
||||||
@@ -514,7 +692,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Some(Err(_)) | None => {
|
||||||
let actual_error_str = actual_error.to_string();
|
let actual_error_str = actual_error.to_string();
|
||||||
if !actual_error_str.contains(expected_error) {
|
if !actual_error_str.contains(expected_error) {
|
||||||
panic_with_listing!(
|
panic_with_listing!(
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ impl TestCCommand {
|
|||||||
if !self.skip_ffi {
|
if !self.skip_ffi {
|
||||||
prepare_ffi_artifacts(self.release, self.frozen)?;
|
prepare_ffi_artifacts(self.release, self.frozen)?;
|
||||||
}
|
}
|
||||||
run_binding("bindings/c", "regorus_test", self.release)
|
run_binding("bindings/c", "regorus_test", self.release)?;
|
||||||
|
run_binding("bindings/c", "regorus_rvm_test", self.release)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ impl TestCppCommand {
|
|||||||
if !self.skip_ffi {
|
if !self.skip_ffi {
|
||||||
prepare_ffi_artifacts(self.release, self.frozen)?;
|
prepare_ffi_artifacts(self.release, self.frozen)?;
|
||||||
}
|
}
|
||||||
run_binding("bindings/cpp", "regorus_test", self.release)
|
run_binding("bindings/cpp", "regorus_test", self.release)?;
|
||||||
|
run_binding("bindings/cpp", "regorus_rvm_test", self.release)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,6 +407,7 @@ fn run_regorus_tests(
|
|||||||
if clean {
|
if clean {
|
||||||
clean_msbuild_project(®orus_tests)?;
|
clean_msbuild_project(®orus_tests)?;
|
||||||
}
|
}
|
||||||
|
println!("Running Regorus.Tests (includes RVM program tests)...");
|
||||||
restore_with_source(
|
restore_with_source(
|
||||||
®orus_tests,
|
®orus_tests,
|
||||||
&property_args,
|
&property_args,
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ impl TestGoCommand {
|
|||||||
run_go_command(&go_dir, ["mod", "tidy"], "go mod tidy (bindings/go)")?;
|
run_go_command(&go_dir, ["mod", "tidy"], "go mod tidy (bindings/go)")?;
|
||||||
run_go_command(&go_dir, ["build"], "go build (bindings/go)")?;
|
run_go_command(&go_dir, ["build"], "go build (bindings/go)")?;
|
||||||
|
|
||||||
|
let lib_dir = ffi_dir.join("target").join(profile);
|
||||||
|
let mut go_test = Command::new("go");
|
||||||
|
go_test.current_dir(&go_dir);
|
||||||
|
go_test.arg("test");
|
||||||
|
go_test.arg("./...");
|
||||||
|
add_library_search_path(&mut go_test, &lib_dir);
|
||||||
|
run_command(go_test, "go test ./... (bindings/go)")?;
|
||||||
|
|
||||||
let binary = go_test_binary(&go_dir);
|
let binary = go_test_binary(&go_dir);
|
||||||
if !binary.exists() {
|
if !binary.exists() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
@@ -48,7 +56,6 @@ impl TestGoCommand {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let lib_dir = ffi_dir.join("target").join(profile);
|
|
||||||
let mut run = Command::new(&binary);
|
let mut run = Command::new(&binary);
|
||||||
run.current_dir(&go_dir);
|
run.current_dir(&go_dir);
|
||||||
add_library_search_path(&mut run, &lib_dir);
|
add_library_search_path(&mut run, &lib_dir);
|
||||||
|
|||||||
Reference in New Issue
Block a user