From 2858b63cd4f6ce5dd846f0eb4fced7b32eeb6fc6 Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com> Date: Tue, 25 Mar 2025 11:31:13 -0700 Subject: [PATCH] feat: Regorus nuget package (#383) Organize C# binding example into separate Regorus nuget package and a test app. The nuget package targets netstandard 2.0 and 2.1. The test app is tested for netframework 8.0. Implement IDisposable for Engine cleanup. Also remove net40 example. Can be added back later if needed. Signed-off-by: Anand Krishnamoorthi --- .github/workflows/test-csharp.yml | 119 ++++++++- .github/workflows/test-csharp40.yml | 28 -- .gitignore | 7 +- bindings/csharp/Regorus/Regorus.cs | 240 ++++++++++++++++++ bindings/csharp/Regorus/Regorus.csproj | 48 ++++ bindings/csharp/Regorus/RegorusFFI.cs | 219 ++++++++++++++++ bindings/csharp/Regorus/docs/README.md | 1 + .../csharp/{net8.0 => TestApp}/Program.cs | 140 +++++----- bindings/csharp/TestApp/TestApp.csproj | 16 ++ bindings/csharp/net40/Program.cs | 65 ----- bindings/csharp/net40/README | 4 - bindings/csharp/net40/Regorus.cs | 220 ---------------- .../csharp/net40/RegorusCsharp-Lib-x64.nuspec | 22 -- bindings/csharp/net40/regorus-test.csproj | 24 -- bindings/csharp/net8.0/Regorus.cs | 236 ----------------- bindings/csharp/net8.0/regorus-test.csproj | 24 -- bindings/ffi/build.rs | 2 +- 17 files changed, 713 insertions(+), 702 deletions(-) delete mode 100644 .github/workflows/test-csharp40.yml create mode 100644 bindings/csharp/Regorus/Regorus.cs create mode 100644 bindings/csharp/Regorus/Regorus.csproj create mode 100644 bindings/csharp/Regorus/RegorusFFI.cs create mode 100644 bindings/csharp/Regorus/docs/README.md rename bindings/csharp/{net8.0 => TestApp}/Program.cs (65%) create mode 100644 bindings/csharp/TestApp/TestApp.csproj delete mode 100644 bindings/csharp/net40/Program.cs delete mode 100644 bindings/csharp/net40/README delete mode 100644 bindings/csharp/net40/Regorus.cs delete mode 100644 bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec delete mode 100644 bindings/csharp/net40/regorus-test.csproj delete mode 100644 bindings/csharp/net8.0/Regorus.cs delete mode 100644 bindings/csharp/net8.0/regorus-test.csproj diff --git a/.github/workflows/test-csharp.yml b/.github/workflows/test-csharp.yml index ce566e6..ff258e8 100644 --- a/.github/workflows/test-csharp.yml +++ b/.github/workflows/test-csharp.yml @@ -6,9 +6,101 @@ on: pull_request: branches: [ "main" ] -jobs: - test: +jobs: + build-ffi: + name: 'Build Regorus FFI: (${{ matrix.runtime.target }})' + runs-on: ${{ matrix.runtime.os }} + strategy: + # let us get failures from other jobs even if one fails + fail-fast: false + matrix: + runtime: + - os: windows-latest + target: x86_64-pc-windows-msvc + libpath: | + **/release/regorus_ffi.dll + **/release/regorus_ffi.pdb + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + libpath: | + **/release/libregorus_ffi.so + # Disabled for now + #- os: macos-latest + # target: aarch64-apple-darwin + # libpath: | + # **/release/libregorus_ffi.dylib + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build Regorus binding + run: cargo build -r --target ${{ matrix.runtime.target }} + working-directory: ./bindings/ffi + + - name: Upload regorus ffi shared library + uses: actions/upload-artifact@v4 + with: + name: regorus-ffi-artifacts-${{ matrix.runtime.target }} + # Note: The full path of each artifact relative to . is preserved. + path: ${{ matrix.runtime.libpath }} + if-no-files-found: error + retention-days: 1 + + build-nuget: + name: 'Build Regorus nuget' runs-on: ubuntu-latest + needs: build-ffi + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-dotnet@v4 + + - name: Download regorus ffi shared libraries + uses: actions/download-artifact@v4 + with: + pattern: regorus-ffi-artifacts-* + merge-multiple: true + path: ./bindings/csharp/Regorus/tmp + + - name: Display regorus ffi artifacts + run: ls -R ./bindings/csharp/Regorus/tmp + + # Note that we need to supply the target folder within the folder where artifacts are downloaded. + - name: Build Regorus binding + run: dotnet build /p:Configuration=Release /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target + working-directory: ./bindings/csharp/Regorus + + - name: Pack + run: dotnet pack /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target + working-directory: ./bindings/csharp/Regorus + + - name: Upload Regorus nuget + uses: actions/upload-artifact@v4 + with: + name: regorus-nuget + path: bindings/csharp/Regorus/bin/Release/Regorus*.nupkg + if-no-files-found: error + retention-days: 1 + + test-nuget: + name: 'Test Regorus Nuget: (${{ matrix.runtime.target }})' + needs: build-nuget + runs-on: ${{ matrix.runtime.os }} + strategy: + # let us get failures from other jobs even if one fails + fail-fast: false + matrix: + runtime: + - os: windows-latest + target: x86_64-pc-windows-msvc + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + #- os: macos-latest + # target: aarch64-apple-darwin steps: - name: Checkout repository uses: actions/checkout@v4 @@ -17,10 +109,21 @@ jobs: - uses: actions/setup-dotnet@v4 - - name: Build - run: dotnet build - working-directory: ./bindings/csharp/net8.0 + - name: Download regorus nuget + uses: actions/download-artifact@v4 + with: + name: regorus-nuget + path: ./bindings/csharp/TestApp/regorus-nuget/ + + - name: Restore Test App + run: dotnet restore /p:RestoreSources=./regorus-nuget/ + working-directory: ./bindings/csharp/TestApp - - name: Run - run: LD_LIBRARY_PATH=. dotnet run - working-directory: ./bindings/csharp/net8.0 + - name: Build Test App 8.0 + run: dotnet build --framework net8.0 + working-directory: ./bindings/csharp/TestApp + + - name: Run Test App 8.0 + run: dotnet run --framework net8.0 + working-directory: ./bindings/csharp/TestApp + \ No newline at end of file diff --git a/.github/workflows/test-csharp40.yml b/.github/workflows/test-csharp40.yml deleted file mode 100644 index bb06ac9..0000000 --- a/.github/workflows/test-csharp40.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: bindings/csharp40 - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - test: - runs-on: windows-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-dotnet@v1 - with: - dotnet-version: "5.0.x" - - - name: Build - run: dotnet build - working-directory: ./bindings/csharp/net40 - - - name: Run - run: dotnet run - working-directory: ./bindings/csharp/net40 diff --git a/.gitignore b/.gitignore index 51f0ed6..4b69e48 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,9 @@ worktrees/ bindings/ffi/regorus.h bindings/ffi/regorus.ffi.hpp -bindings/*/target \ No newline at end of file +bindings/*/target + +# C# build folders +**bin +**obj +*.sln \ No newline at end of file diff --git a/bindings/csharp/Regorus/Regorus.cs b/bindings/csharp/Regorus/Regorus.cs new file mode 100644 index 0000000..69c5e9a --- /dev/null +++ b/bindings/csharp/Regorus/Regorus.cs @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.InteropServices; +using System.Text; + + +#nullable enable +namespace Regorus +{ + public unsafe sealed class Engine : System.IDisposable + { + private Regorus.Internal.RegorusEngine* E; + // Detect redundant Dispose() calls in a thread-safe manner. + // _isDisposed == 0 means Dispose(bool) has not been called yet. + // _isDisposed == 1 means Dispose(bool) has been already called. + private int isDisposed; + + public Engine() + { + E = Regorus.Internal.API.regorus_engine_new(); + } + + public void Dispose() + { + Dispose(disposing: true); + + // This object will be cleaned up by the Dispose method. + // Therefore, call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + // Dispose(bool disposing) executes in two distinct scenarios. + // If disposing equals true, the method has been called directly + // or indirectly by a user's code. Managed and unmanaged resources + // can be disposed. + // If disposing equals false, the method has been called by the + // runtime from inside the finalizer and you should not reference + // other objects. Only unmanaged resources can be disposed. + void Dispose(bool disposing) + { + // In case _isDisposed is 0, atomically set it to 1. + // Enter the branch only if the original value is 0. + if (System.Threading.Interlocked.CompareExchange(ref isDisposed, 1, 0) == 0) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + // No managed resource to dispose. + } + + // Call the appropriate methods to clean up + // unmanaged resources here. + // If disposing is false, + // only the following code is executed. + if (E != null) + { + Regorus.Internal.API.regorus_engine_drop(E); + E = null; + } + + } + } + + // Use C# finalizer syntax for finalization code. + // This finalizer will run only if the Dispose method + // does not get called. + ~Engine() => Dispose(disposing: false); + + // Helper for implementing Clone + private Engine(Internal.RegorusEngine* engine) + { + this.E = engine; + } + + public Engine Clone() => new(Internal.API.regorus_engine_clone(E)); + + byte[] NullTerminatedUTF8Bytes(string s) + { + return Encoding.UTF8.GetBytes(s + char.MinValue); + } + + public string? AddPolicy(string path, string rego) + { + var pathBytes = NullTerminatedUTF8Bytes(path); + var regoBytes = NullTerminatedUTF8Bytes(rego); + + + fixed (byte* pathPtr = pathBytes) + { + fixed (byte* regoPtr = regoBytes) + { + return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy(E, pathPtr, regoPtr)); + } + } + + } + + public void SetRegoV0(bool enable) + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0(E, enable)); + } + + public string? AddPolicyFromFile(string path) + { + var pathBytes = NullTerminatedUTF8Bytes(path); + fixed (byte* pathPtr = pathBytes) + { + return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file(E, pathPtr)); + } + + } + + public void AddDataJson(string data) + { + var dataBytes = NullTerminatedUTF8Bytes(data); + fixed (byte* dataPtr = dataBytes) + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json(E, dataPtr)); + } + + } + + public void AddDataFromJsonFile(string path) + { + var pathBytes = NullTerminatedUTF8Bytes(path); + fixed (byte* pathPtr = pathBytes) + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file(E, pathPtr)); + } + + } + + public void SetInputJson(string input) + { + var inputBytes = NullTerminatedUTF8Bytes(input); + fixed (byte* inputPtr = inputBytes) + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json(E, inputPtr)); + } + } + + public void SetInputFromJsonFile(string path) + { + var pathBytes = NullTerminatedUTF8Bytes(path); + fixed (byte* pathPtr = pathBytes) + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file(E, pathPtr)); + } + } + + public string? EvalQuery(string query) + { + var queryBytes = NullTerminatedUTF8Bytes(query); + fixed (byte* queryPtr = queryBytes) + { + return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query(E, queryPtr)); + } + } + + public string? EvalRule(string rule) + { + var ruleBytes = NullTerminatedUTF8Bytes(rule); + fixed (byte* rulePtr = ruleBytes) + { + return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query(E, rulePtr)); + } + } + + public void SetEnableCoverage(bool enable) + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage(E, enable)); + } + + public void ClearCoverageData() + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data(E)); + } + + public string? GetCoverageReport() + { + return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report(E)); + } + + public string? GetCoverageReportPretty() + { + return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty(E)); + } + + public void SetGatherPrints(bool enable) + { + CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints(E, enable)); + } + + public string? TakePrints() + { + return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints(E)); + } + + + + string? StringFromUTF8(IntPtr ptr) + { + +#if NETSTANDARD2_1 + return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr); +#else + int len = 0; + while (Marshal.ReadByte(ptr, len) != 0) { ++len; } + byte[] buffer = new byte[len]; + Marshal.Copy(ptr, buffer, 0, buffer.Length); + return Encoding.UTF8.GetString(buffer); +#endif + } + + string? CheckAndDropResult(Regorus.Internal.RegorusResult result) + { + if (result.status != Regorus.Internal.RegorusStatus.RegorusStatusOk) + { + var message = StringFromUTF8((IntPtr)result.error_message); + var ex = new Exception(message); + Regorus.Internal.API.regorus_result_drop(result); + throw ex; + } + + var resultString = ""; + if (result.output is not null) + { + resultString = StringFromUTF8((IntPtr)result.output); + } + Regorus.Internal.API.regorus_result_drop(result); + return resultString; + } + } +} diff --git a/bindings/csharp/Regorus/Regorus.csproj b/bindings/csharp/Regorus/Regorus.csproj new file mode 100644 index 0000000..39686cb --- /dev/null +++ b/bindings/csharp/Regorus/Regorus.csproj @@ -0,0 +1,48 @@ + + + + Library + Microsoft.Regorus + netstandard2.0;netstandard2.1 + true + 0.4.0-preview.1 + 10.0 + README.md + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/csharp/Regorus/RegorusFFI.cs b/bindings/csharp/Regorus/RegorusFFI.cs new file mode 100644 index 0000000..796c6cb --- /dev/null +++ b/bindings/csharp/Regorus/RegorusFFI.cs @@ -0,0 +1,219 @@ +// +// This code is generated by csbindgen. +// DON'T CHANGE THIS DIRECTLY. +// +#pragma warning disable CS8500 +#pragma warning disable CS8981 +using System; +using System.Runtime.InteropServices; + + +namespace Regorus.Internal +{ + internal static unsafe partial class API + { + const string __DllName = "regorus_ffi"; + + + + /// + /// Drop a `RegorusResult`. + /// + /// `output` and `error_message` strings are not valid after drop. + /// + [DllImport(__DllName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void regorus_result_drop(RegorusResult r); + + /// + /// Construct a new Engine + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusEngine* regorus_engine_new(); + + /// + /// Clone a [`RegorusEngine`] + /// + /// To avoid having to parse same policy again, the engine can be cloned + /// after policies and data have been added. + /// + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine); + + [DllImport(__DllName, EntryPoint = "regorus_engine_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void regorus_engine_drop(RegorusEngine* engine); + + /// + /// Add a policy + /// + /// The policy is parsed into AST. + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy + /// + /// * `path`: A filename to be associated with the policy. + /// * `rego`: Rego policy. + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_add_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_add_policy(RegorusEngine* engine, byte* path, byte* rego); + + [DllImport(__DllName, EntryPoint = "regorus_engine_add_policy_from_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_add_policy_from_file(RegorusEngine* engine, byte* path); + + /// + /// Add policy data. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data + /// * `data`: JSON encoded value to be used as policy data. + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_add_data_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_add_data_json(RegorusEngine* engine, byte* data); + + /// + /// Get list of loaded Rego packages as JSON. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_get_packages", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_get_packages(RegorusEngine* engine); + + /// + /// Get list of policies as JSON. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_get_policies", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_get_policies(RegorusEngine* engine); + + [DllImport(__DllName, EntryPoint = "regorus_engine_add_data_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine* engine, byte* path); + + /// + /// Clear policy data. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_clear_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_clear_data(RegorusEngine* engine); + + /// + /// Set input. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input + /// * `input`: JSON encoded value to be used as input to query. + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_set_input_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_set_input_json(RegorusEngine* engine, byte* input); + + [DllImport(__DllName, EntryPoint = "regorus_engine_set_input_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine* engine, byte* path); + + /// + /// Evaluate query. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query + /// * `query`: Rego expression to be evaluate. + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_eval_query", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_eval_query(RegorusEngine* engine, byte* query); + + /// + /// Evaluate specified rule. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule + /// * `rule`: Path to the rule. + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_eval_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_eval_rule(RegorusEngine* engine, byte* rule); + + /// + /// Enable/disable coverage. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage + /// * `enable`: Whether to enable or disable coverage. + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_set_enable_coverage", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_set_enable_coverage(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable); + + /// + /// Get coverage report. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_get_coverage_report", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_get_coverage_report(RegorusEngine* engine); + + /// + /// Get pretty printed coverage report. + /// + /// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_get_coverage_report_pretty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_get_coverage_report_pretty(RegorusEngine* engine); + + /// + /// Clear coverage data. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_clear_coverage_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_clear_coverage_data(RegorusEngine* engine); + + /// + /// Whether to gather output of print statements. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints + /// * `enable`: Whether to enable or disable gathering print statements. + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_set_gather_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_set_gather_prints(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable); + + /// + /// Take all the gathered print statements. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_take_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_take_prints(RegorusEngine* engine); + + /// + /// Get AST of policies. + /// + /// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_ast_as_json + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_get_ast_as_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_get_ast_as_json(RegorusEngine* engine); + + /// + /// Enable/disable rego v1. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0 + /// + [DllImport(__DllName, EntryPoint = "regorus_engine_set_rego_v0", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern RegorusResult regorus_engine_set_rego_v0(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable); + + + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct RegorusResult + { + public RegorusStatus status; + public byte* output; + public byte* error_message; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct RegorusEngine + { + } + + + internal enum RegorusStatus : uint + { + RegorusStatusOk, + RegorusStatusError, + } + + +} diff --git a/bindings/csharp/Regorus/docs/README.md b/bindings/csharp/Regorus/docs/README.md new file mode 100644 index 0000000..28c2dd0 --- /dev/null +++ b/bindings/csharp/Regorus/docs/README.md @@ -0,0 +1 @@ +C# Bindings for Regorus \ No newline at end of file diff --git a/bindings/csharp/net8.0/Program.cs b/bindings/csharp/TestApp/Program.cs similarity index 65% rename from bindings/csharp/net8.0/Program.cs rename to bindings/csharp/TestApp/Program.cs index fff52a9..565470b 100644 --- a/bindings/csharp/net8.0/Program.cs +++ b/bindings/csharp/TestApp/Program.cs @@ -1,69 +1,71 @@ -//----------------------------------------------------------------------- -// -// Copyright (c)2012 Microsoft. All rights reserved. -// -// -// Contains code to test the Regorus class for C# -// and .NET 8.0 bindings. -// -//----------------------------------------------------------------------- - -using System.Diagnostics; - -long nanosecPerTick = (1000L*1000L*1000L) / Stopwatch.Frequency; -var w = new Stopwatch(); - - -// Force load of modules. -{ - var _e = new Regorus.Engine(); - var _j = System.Text.Json.JsonDocument.Parse("{}"); -} - -w.Restart(); - -var engine = new Regorus.Engine(); -engine.SetRegoV0(true); - -w.Stop(); -var newEngineTicks = w.ElapsedTicks; - - -w.Restart(); - -// Load policies and data. -engine.AddPolicyFromFile("../../../tests/aci/framework.rego"); -engine.AddPolicyFromFile("../../../tests/aci/api.rego"); -engine.AddPolicyFromFile("../../../tests/aci/policy.rego"); -engine.AddDataFromJsonFile("../../../tests/aci/data.json"); - - -w.Stop(); -var loadPoliciesTicks = w.ElapsedTicks; - - -w.Restart(); - -// Set input and eval rule. -engine.SetInputFromJsonFile("../../../tests/aci/input.json"); -var value = engine.EvalQuery("data.framework.mount_overlay"); -var valueDoc = System.Text.Json.JsonDocument.Parse(value); - -w.Stop(); -var evalTicks = w.ElapsedTicks; - -Console.WriteLine("{0}", valueDoc); - - -Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks*nanosecPerTick)/(1000.0*1000.0)); -Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks*nanosecPerTick)/(1000.0*1000.0)); -Console.WriteLine("EvalQuery took {0} msecs", (evalTicks*nanosecPerTick)/(1000.0*1000.0)); - -engine = new Regorus.Engine(); -engine.AddPolicy( - "test.rego", - "package test\nx = 1\nmessage = `Hello`"); - -engine.SetEnableCoverage(true); -Console.WriteLine("{0}", engine.EvalRule("data.test.message")); -Console.WriteLine("{0}", engine.GetCoverageReportPretty()); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; + + +long nanosecPerTick = (1000L * 1000L * 1000L) / Stopwatch.Frequency; +var w = new Stopwatch(); + + +// Force load of modules. +{ + var _e = new Regorus.Engine(); +#if NET8_0_OR_GREATER + var _j = System.Text.Json.JsonDocument.Parse("{}"); +#endif +} + +w.Restart(); + +var engine = new Regorus.Engine(); +engine.SetRegoV0(true); + +w.Stop(); +var newEngineTicks = w.ElapsedTicks; + + +w.Restart(); + +// Load policies and data. +engine.AddPolicyFromFile("../../../tests/aci/framework.rego"); +engine.AddPolicyFromFile("../../../tests/aci/api.rego"); +engine.AddPolicyFromFile("../../../tests/aci/policy.rego"); +engine.AddDataFromJsonFile("../../../tests/aci/data.json"); + + +w.Stop(); +var loadPoliciesTicks = w.ElapsedTicks; + + +w.Restart(); + +// Set input and eval rule. +engine.SetInputFromJsonFile("../../../tests/aci/input.json"); +var value = engine.EvalQuery("data.framework.mount_overlay"); + +#if NET8_0_OR_GREATER +var valueDoc = System.Text.Json.JsonDocument.Parse(value); + +w.Stop(); +var evalTicks = w.ElapsedTicks; + +Console.WriteLine("{0}", valueDoc); +#else +w.Stop(); +var evalTicks = w.ElapsedTicks; +#endif + + +Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks * nanosecPerTick) / (1000.0 * 1000.0)); +Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks * nanosecPerTick) / (1000.0 * 1000.0)); +Console.WriteLine("EvalQuery took {0} msecs", (evalTicks * nanosecPerTick) / (1000.0 * 1000.0)); + +engine = new Regorus.Engine(); +engine.AddPolicy( + "test.rego", + "package test\nx = 1\nmessage = `Hello`"); + +engine.SetEnableCoverage(true); +Console.WriteLine("{0}", engine.EvalRule("data.test.message")); +Console.WriteLine("{0}", engine.GetCoverageReportPretty()); diff --git a/bindings/csharp/TestApp/TestApp.csproj b/bindings/csharp/TestApp/TestApp.csproj new file mode 100644 index 0000000..4f8ddc0 --- /dev/null +++ b/bindings/csharp/TestApp/TestApp.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + TestApp + enable + enable + true + 10.0 + + + + + + diff --git a/bindings/csharp/net40/Program.cs b/bindings/csharp/net40/Program.cs deleted file mode 100644 index 6236564..0000000 --- a/bindings/csharp/net40/Program.cs +++ /dev/null @@ -1,65 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c)2012 Microsoft. All rights reserved. -// -// -// Contains code to test the Regorus Policy Engine base class for C# -// and .NET4.0 bindings. It can be built and tested in Windows only. -// -//----------------------------------------------------------------------- -using System; -using System.Text; - -using System.Diagnostics; -using Microsoft.WindowsAzure.Regorus.IaaS; - -namespace regoregorus_test -{ - class Program - { - static void Main(string[] args) - { - long nanosecPerTick = (1000L * 1000L * 1000L) / Stopwatch.Frequency; - var w = new Stopwatch(); - w.Restart(); - - var engine = new RegorusPolicyEngine(); - - w.Stop(); - var newEngineTicks = w.ElapsedTicks; - - - w.Restart(); - - // Load policies and data. - engine.AddPolicyFromFile("../../../examples/extension_list/agent_extension_policy.rego"); - engine.AddDataFromJsonFile("../../../examples/extension_list/agent-extension-data-allow-only.json"); - - - w.Stop(); - var loadPoliciesTicks = w.ElapsedTicks; - - - w.Restart(); - - // Set input and eval query. - engine.SetInputFromJsonFile("../../../examples/extension_list/agent-extension-input.json"); - var results = engine.EvalQuery("data.agent_extension_policy.extensions_to_download=x"); - Console.WriteLine("Download query test: \n {0}", results); - - results = engine.EvalQuery("data.agent_extension_policy.extensions_validated"); - - Console.WriteLine("Signing validation test: \n {0}", results); - - engine.Dispose(); - - w.Stop(); - var evalTicks = w.ElapsedTicks; - - Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks * nanosecPerTick) / (1000.0 * 1000.0)); - Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks * nanosecPerTick) / (1000.0 * 1000.0)); - Console.WriteLine("EvalQuery and print results took {0} msecs", (evalTicks * nanosecPerTick) / (1000.0 * 1000.0)); - } - } -} - diff --git a/bindings/csharp/net40/README b/bindings/csharp/net40/README deleted file mode 100644 index 68bf4d3..0000000 --- a/bindings/csharp/net40/README +++ /dev/null @@ -1,4 +0,0 @@ -The Regorus C# binding library can be built via command "dotnet build". We can use the Regorus C# binding library built from this -directory to create a Nuget. This Nuget will contain the Regorus C# binding library with definitions that -work for .NET framework 4.0 (net40) and above. Note the Nuget can only be created after the binding library has been built. -RegorusCsharp-Lib-x64.nuspec is built for x64 architecture. \ No newline at end of file diff --git a/bindings/csharp/net40/Regorus.cs b/bindings/csharp/net40/Regorus.cs deleted file mode 100644 index c995e63..0000000 --- a/bindings/csharp/net40/Regorus.cs +++ /dev/null @@ -1,220 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c)2012 Microsoft. All rights reserved. -// -// -// Contains code for the Regorus Policy Engine base class for C# and -// .NET4.0 bindings. Currently this base class is not thread-safe. Make -// sure we use it in a signle-threaded environment or add additional -// protection when using it. -// -//----------------------------------------------------------------------- - - -using System; -using System.Text; -using System.IO; -using System.Threading; - -namespace Microsoft.WindowsAzure.Regorus.IaaS -{ - - public class RegorusPolicyEngine : ICloneable, IDisposable - { - unsafe private RegorusFFI.RegorusEngine* E; - - public RegorusPolicyEngine() - { - unsafe - { - E = RegorusFFI.API.regorus_engine_new(); - } - } - - - public void Dispose() - { - unsafe - { - if (E != null) - { - RegorusFFI.API.regorus_engine_drop(E); - // to avoid Dispose() being called multiple times by mistake. - E = null; - } - - } - - } - - public object Clone() - { - var clone = (RegorusPolicyEngine)this.MemberwiseClone(); - unsafe - { - clone.E = RegorusFFI.API.regorus_engine_clone(E); - } - return clone; - - } - - byte[] NullTerminatedUTF8Bytes(string s) - { - return Encoding.UTF8.GetBytes(s + char.MinValue); - } - public void SetRegoV0(bool enable) - { - unsafe - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable)); - } - } - - public void AddPolicy(string path, string rego) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - var regoBytes = NullTerminatedUTF8Bytes(rego); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - fixed (byte* regoPtr = regoBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr)); - } - } - } - } - - public void AddPolicyFromFile(string path) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr)); - } - } - } - - public void AddPolicyFromPath(string path) - { - if (!Directory.Exists(path)) - { - return; - } - - string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories); - foreach (string file in regoFiles) - { - AddPolicyFromFile(file); - } - } - - public void AddDataJson(string data) - { - var dataBytes = NullTerminatedUTF8Bytes(data); - - unsafe - { - fixed (byte* dataPtr = dataBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr)); - } - } - } - - public void AddDataFromJsonFile(string path) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr)); - - } - } - } - - public void SetInputJson(string input) - { - var inputBytes = NullTerminatedUTF8Bytes(input); - - unsafe - { - fixed (byte* inputPtr = inputBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr)); - } - } - } - - public void SetInputFromJsonFile(string path) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr)); - } - } - } - - public string EvalQuery(string query) - { - var queryBytes = NullTerminatedUTF8Bytes(query); - - var resultJson = ""; - unsafe - { - fixed (byte* queryPtr = queryBytes) - { - var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr); - if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk) - { - if (result.output != null) - { - resultJson = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.output); - } - RegorusFFI.API.regorus_result_drop(result); - } - else - { - CheckAndDropResult(result); - } - } - } - if (resultJson != null) - { - return resultJson; - } - else - { - return ""; - } - } - - void CheckAndDropResult(RegorusFFI.RegorusResult result) - { - if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) - { - unsafe - { - var message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.error_message); - var ex = new Exception(message); - RegorusFFI.API.regorus_result_drop(result); - throw ex; - } - } - RegorusFFI.API.regorus_result_drop(result); - } - - } -} diff --git a/bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec b/bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec deleted file mode 100644 index 8f8956c..0000000 --- a/bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec +++ /dev/null @@ -1,22 +0,0 @@ - - - - RegorusCsharp-Lib-x64 - 0.2.1 - RegorusCsharp-Lib-x64 - yangjie@microsoft.com - yangjie@microsoft.com - https://www.microsoft.com - false - Regorus C# library for x64 - remove Regorus.cs from Nuget - Copyright (C) Microsoft Corp - - - - - - - - - \ No newline at end of file diff --git a/bindings/csharp/net40/regorus-test.csproj b/bindings/csharp/net40/regorus-test.csproj deleted file mode 100644 index 3e06e3d..0000000 --- a/bindings/csharp/net40/regorus-test.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - Exe - net40 - regorus_test - regoregorus_test.Program - enable - true - - - - - diff --git a/bindings/csharp/net8.0/Regorus.cs b/bindings/csharp/net8.0/Regorus.cs deleted file mode 100644 index 51aa0da..0000000 --- a/bindings/csharp/net8.0/Regorus.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System.Text; - -namespace Regorus -{ - public class Exception : System.Exception - { - public Exception(string? message) : base(message) { } - } - - public class Engine : ICloneable - { - unsafe private RegorusFFI.RegorusEngine* E; - public Engine() - { - unsafe - { - E = RegorusFFI.API.regorus_engine_new(); - } - } - - public object Clone() - { - var clone = (Engine)this.MemberwiseClone(); - unsafe - { - clone.E = RegorusFFI.API.regorus_engine_clone(E); - } - return clone; - - } - - byte[] NullTerminatedUTF8Bytes(string s) - { - return Encoding.UTF8.GetBytes(s + char.MinValue); - } - - public string AddPolicy(string path, string rego) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - var regoBytes = NullTerminatedUTF8Bytes(rego); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - fixed (byte* regoPtr = regoBytes) - { - return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr)); - } - } - } - } - - public void SetRegoV0(bool enable) - { - unsafe - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable)); - } - } - - public string AddPolicyFromFile(string path) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr)); - } - } - } - - public void AddDataJson(string data) - { - var dataBytes = NullTerminatedUTF8Bytes(data); - - unsafe - { - fixed (byte* dataPtr = dataBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr)); - } - } - } - - public void AddDataFromJsonFile(string path) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr)); - } - } - } - - public void SetInputJson(string input) - { - var inputBytes = NullTerminatedUTF8Bytes(input); - - unsafe - { - fixed (byte* inputPtr = inputBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr)); - } - } - } - - public void SetInputFromJsonFile(string path) - { - var pathBytes = NullTerminatedUTF8Bytes(path); - - unsafe - { - fixed (byte* pathPtr = pathBytes) - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr)); - } - } - } - - public string EvalQuery(string query) - { - var queryBytes = NullTerminatedUTF8Bytes(query); - - unsafe - { - fixed (byte* queryPtr = queryBytes) - { - return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr)); - } - } - } - - public string EvalRule(string rule) - { - var ruleBytes = NullTerminatedUTF8Bytes(rule); - - unsafe - { - fixed (byte* rulePtr = ruleBytes) - { - return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr)); - } - } - } - - public void SetEnableCoverage(bool enable) - { - unsafe - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable)); - } - } - - public void ClearCoverageData() - { - unsafe - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E)); - } - } - - public string GetCoverageReport() - { - unsafe - { - return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E)); - } - } - - public string GetCoverageReportPretty() - { - unsafe - { - return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E)); - } - } - - public void SetGatherPrints(bool enable) - { - unsafe - { - CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable)); - } - } - - public string TakePrints() - { - unsafe - { - return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E)); - } - } - - ~Engine() - { - unsafe - { - RegorusFFI.API.regorus_engine_drop(E); - } - } - - - string CheckAndDropResult(RegorusFFI.RegorusResult result) - { - if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) - { - unsafe - { - var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message); - var ex = new Exception(message); - RegorusFFI.API.regorus_result_drop(result); - throw ex; - } - } - - var resultString = ""; - unsafe - { - if (result.output is not null) - { - resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output); - } - RegorusFFI.API.regorus_result_drop(result); - } - return resultString; - } - - } -} diff --git a/bindings/csharp/net8.0/regorus-test.csproj b/bindings/csharp/net8.0/regorus-test.csproj deleted file mode 100644 index 05fec4b..0000000 --- a/bindings/csharp/net8.0/regorus-test.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - Exe - net8.0 - regorus_test - enable - enable - true - - - - - diff --git a/bindings/ffi/build.rs b/bindings/ffi/build.rs index 5c0ca06..ac20a59 100644 --- a/bindings/ffi/build.rs +++ b/bindings/ffi/build.rs @@ -26,7 +26,7 @@ fn main() { .input_extern_file("src/lib.rs") .csharp_dll_name("regorus_ffi") .csharp_class_name("API") - .csharp_namespace("RegorusFFI") + .csharp_namespace("Regorus.Internal") .generate_csharp_file("./RegorusFFI.g.cs") .unwrap(); }