// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Text; using System.Text.Json; #nullable enable namespace Regorus { /// /// Represents a compiled Regorus policy that can be evaluated efficiently. /// This class wraps a pre-compiled policy that can be evaluated multiple times /// with different inputs without recompilation overhead. /// /// This class manages unmanaged resources and should not be copied or cloned. /// Each instance represents a unique native policy object. /// /// Thread Safety: This class is thread-safe for all operations. Multiple threads /// can safely call EvalWithInput() concurrently, and Dispose() will safely wait /// for all active evaluations to complete before freeing resources. No external /// synchronization is required. /// public unsafe sealed class CompiledPolicy : IDisposable { private Internal.RegorusCompiledPolicy* _policy; private int _isDisposed; private int _activeEvaluations; internal CompiledPolicy(Internal.RegorusCompiledPolicy* policy) { _policy = policy; } /// /// Evaluates the compiled policy with the given input. /// For target policies, evaluates the target's effect rule. /// For regular policies, evaluates the originally compiled rule. /// /// JSON encoded input data (resource) to validate against the policy /// The evaluation result as JSON string /// Thrown when policy evaluation fails /// Thrown when the policy has been disposed public string? EvalWithInput(string inputJson) { // Increment active evaluations count System.Threading.Interlocked.Increment(ref _activeEvaluations); try { ThrowIfDisposed(); var inputBytes = Encoding.UTF8.GetBytes(inputJson + char.MinValue); fixed (byte* inputPtr = inputBytes) { return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input(_policy, inputPtr)); } } finally { // Decrement active evaluations count System.Threading.Interlocked.Decrement(ref _activeEvaluations); } } /// /// Gets information about the compiled policy including metadata about modules, /// target configuration, and resource types. /// /// Policy information containing module IDs, target name, applicable resource types, entry point rule, and parameters /// Thrown when getting policy info fails /// Thrown when the policy has been disposed public PolicyInfo GetPolicyInfo() { ThrowIfDisposed(); var jsonResult = CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info(_policy)); if (string.IsNullOrEmpty(jsonResult)) { throw new Exception("Failed to get policy info: empty response"); } try { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; return JsonSerializer.Deserialize(jsonResult!, options) ?? throw new Exception("Failed to deserialize policy info"); } catch (JsonException ex) { throw new Exception($"Failed to parse policy info JSON: {ex.Message}", ex); } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) { if (_policy != null) { // Wait for all active evaluations to complete while (System.Threading.Volatile.Read(ref _activeEvaluations) > 0) { System.Threading.Thread.Yield(); } Internal.API.regorus_compiled_policy_drop(_policy); _policy = null; } } } ~CompiledPolicy() => Dispose(disposing: false); private void ThrowIfDisposed() { if (_isDisposed != 0) throw new ObjectDisposedException(nameof(CompiledPolicy)); } private string? StringFromUTF8(IntPtr ptr) { #if NETSTANDARD2_1 return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr); #else int len = 0; while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; } byte[] buffer = new byte[len]; System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length); return Encoding.UTF8.GetString(buffer); #endif } private string? CheckAndDropResult(Internal.RegorusResult result) { try { if (result.status != Internal.RegorusStatus.Ok) { var message = StringFromUTF8((IntPtr)result.error_message); throw new Exception(message ?? "Unknown error occurred"); } return result.data_type switch { Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output), Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(), Internal.RegorusDataType.Integer => result.int_value.ToString(), Internal.RegorusDataType.None => null, _ => StringFromUTF8((IntPtr)result.output) }; } finally { Internal.API.regorus_result_drop(result); } } } }