From f25121b78eb630e18351f74e4d993ca6ebe74e83 Mon Sep 17 00:00:00 2001 From: Aayush Tiwari Date: Mon, 13 Jul 2026 17:20:44 +0530 Subject: [PATCH] Implement UUID builtins Signed-off-by: Aayush Tiwari --- .../opa/ast/builtin/BuiltinRegistry.java | 1 + .../opa/ast/builtin/impls/UUIDBuiltins.java | 218 ++++++++++++++++++ .../ast/builtin/impls/UUIDBuiltinsTest.java | 96 ++++++++ 3 files changed, 315 insertions(+) create mode 100644 opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java create mode 100644 opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java diff --git a/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/BuiltinRegistry.java b/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/BuiltinRegistry.java index 17a055b2..58b526fa 100644 --- a/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/BuiltinRegistry.java +++ b/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/BuiltinRegistry.java @@ -29,6 +29,7 @@ public class BuiltinRegistry { HexBuiltins.class, StringBuiltins.class, PrintBuiltins.class, + UUIDBuiltins.class, }; public static final Map> diff --git a/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java b/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java new file mode 100644 index 00000000..b78a30d6 --- /dev/null +++ b/opa-evaluator/src/main/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltins.java @@ -0,0 +1,218 @@ +package io.github.open_policy_agent.opa.ast.builtin.impls; + +import static io.github.open_policy_agent.opa.ast.builtin.impls.utils.ArgHelper.getArg; + +import io.github.open_policy_agent.opa.ast.builtin.OpaBuiltin; +import io.github.open_policy_agent.opa.ast.builtin.OpaType; +import io.github.open_policy_agent.opa.ast.types.RegoBigInt; +import io.github.open_policy_agent.opa.ast.types.RegoInt32; +import io.github.open_policy_agent.opa.ast.types.RegoObject; +import io.github.open_policy_agent.opa.ast.types.RegoString; +import io.github.open_policy_agent.opa.ast.types.RegoUndefined; +import io.github.open_policy_agent.opa.ast.types.RegoValue; +import io.github.open_policy_agent.opa.rego.EvaluationContext; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiFunction; + +public class UUIDBuiltins { + + private static final long UUID_EPOCH_OFFSET_100NS = 122192928000000000L; + + public static Map> builtins() { + UUIDBuiltins instance = new UUIDBuiltins(); + return Map.of( + "uuid.parse", instance::parse, + "uuid.rfc4122", instance::rfc4122); + } + + @OpaBuiltin( + name = "uuid.parse", + description = "Parses a UUID string into its RFC 4122 metadata.", + categories = {"uuid"}, + args = {@OpaType(type = "string", name = "uuid", description = "UUID string to parse")}, + result = @OpaType(type = "object", name = "result", description = "parsed UUID metadata")) + public RegoValue parse(EvaluationContext ctx, RegoValue[] args) { + String input = getArg(args, 0, RegoString.class).getValue(); + UUID uuid = parseUuid(input); + if (uuid == null) { + return RegoUndefined.INSTANCE; + } + + RegoObject result = new RegoObject(); + int version = uuid.version(); + result.setProperty("variant", new RegoString(variantName(uuid.variant()))); + result.setProperty("version", RegoInt32.of(version)); + + if (version == 1 || version == 2) { + byte[] bytes = toBytes(uuid); + result.setProperty("clocksequence", RegoInt32.of(clockSequence(uuid))); + result.setProperty("macvariables", new RegoString(macVariables(bytes[10]))); + result.setProperty("nodeid", new RegoString(nodeId(bytes))); + result.setProperty("time", new RegoBigInt((timestamp(uuid) - UUID_EPOCH_OFFSET_100NS) * 100L)); + + if (version == 2) { + result.setProperty("domain", new RegoString(domain(bytes[9]))); + result.setProperty("id", RegoInt32.of(ByteBuffer.wrap(bytes, 0, 4).getInt())); + } + } + + return result; + } + + @OpaBuiltin( + name = "uuid.rfc4122", + description = "Returns a version 4 RFC 4122 UUID.", + categories = {"uuid"}, + args = { + @OpaType( + type = "string", + name = "key", + description = "cache key for deterministic UUID generation during evaluation") + }, + result = @OpaType(type = "string", name = "uuid", description = "RFC 4122 UUID"), + nondeterministic = true) + public RegoString rfc4122(EvaluationContext ctx, RegoValue[] args) { + if (ctx != null && ctx.getNdBuiltinCache() != null) { + RegoValue cachedValue = ctx.getNdBuiltinCache().get("uuid.rfc4122", args); + if (cachedValue != null) { + return (RegoString) cachedValue; + } + } + + String key = getArg(args, 0, RegoString.class).getValue(); + RegoString result = new RegoString(deterministicVersion4Uuid(key)); + + if (ctx != null) { + if (ctx.getNdBuiltinCache() != null) { + ctx.getNdBuiltinCache().put("uuid.rfc4122", args, result); + } + ctx.recordNdCacheValue("uuid.rfc4122", args, result); + } + + return result; + } + + private UUID parseUuid(String input) { + String normalized = normalize(input); + if (normalized == null) { + return null; + } + + try { + return UUID.fromString(normalized); + } catch (IllegalArgumentException e) { + return null; + } + } + + private String normalize(String input) { + String value = input; + if (value.startsWith("urn:uuid:")) { + value = value.substring("urn:uuid:".length()); + } + if (value.startsWith("{") && value.endsWith("}")) { + value = value.substring(1, value.length() - 1); + } + if (value.length() == 32) { + value = + value.substring(0, 8) + + "-" + + value.substring(8, 12) + + "-" + + value.substring(12, 16) + + "-" + + value.substring(16, 20) + + "-" + + value.substring(20); + } + return value; + } + + private long timestamp(UUID uuid) { + long most = uuid.getMostSignificantBits(); + long timeLow = (most >>> 32) & 0xffffffffL; + long timeMid = (most >>> 16) & 0xffffL; + long timeHigh = most & 0x0fffL; + return (timeHigh << 48) | (timeMid << 32) | timeLow; + } + + private int clockSequence(UUID uuid) { + return (int) ((uuid.getLeastSignificantBits() >>> 48) & 0x3fffL); + } + + private String variantName(int variant) { + switch (variant) { + case 0: + return "Reserved"; + case 2: + return "RFC4122"; + case 6: + return "Microsoft"; + default: + return "Future"; + } + } + + private String domain(byte value) { + switch (Byte.toUnsignedInt(value)) { + case 0: + return "Person"; + case 1: + return "Group"; + case 2: + return "Org"; + default: + return "Invalid"; + } + } + + private String macVariables(byte value) { + int bits = value & 0b11; + switch (bits) { + case 0b11: + return "local:multicast"; + case 0b01: + return "global:multicast"; + case 0b10: + return "local:unicast"; + default: + return "global:unicast"; + } + } + + private String nodeId(byte[] bytes) { + StringBuilder result = new StringBuilder(17); + for (int i = 10; i < bytes.length; i++) { + if (i > 10) { + result.append("-"); + } + result.append(String.format("%02x", Byte.toUnsignedInt(bytes[i]))); + } + return result.toString(); + } + + private byte[] toBytes(UUID uuid) { + ByteBuffer buffer = ByteBuffer.wrap(new byte[16]); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + return buffer.array(); + } + + private String deterministicVersion4Uuid(String key) { + try { + byte[] bytes = + MessageDigest.getInstance("SHA-256").digest(key.getBytes(StandardCharsets.UTF_8)); + bytes[6] = (byte) ((bytes[6] & 0x0f) | 0x40); + bytes[8] = (byte) ((bytes[8] & 0x3f) | 0x80); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + return new UUID(buffer.getLong(), buffer.getLong()).toString(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("SHA-256 digest is not available", e); + } + } +} diff --git a/opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java b/opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java new file mode 100644 index 00000000..6d24912f --- /dev/null +++ b/opa-evaluator/src/test/java/io/github/open_policy_agent/opa/ast/builtin/impls/UUIDBuiltinsTest.java @@ -0,0 +1,96 @@ +package io.github.open_policy_agent.opa.ast.builtin.impls; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.github.open_policy_agent.opa.ast.builtin.BuiltinRegistry; +import io.github.open_policy_agent.opa.ast.types.RegoBigInt; +import io.github.open_policy_agent.opa.ast.types.RegoInt32; +import io.github.open_policy_agent.opa.ast.types.RegoObject; +import io.github.open_policy_agent.opa.ast.types.RegoString; +import io.github.open_policy_agent.opa.ast.types.RegoUndefined; +import io.github.open_policy_agent.opa.ast.types.RegoValue; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class UUIDBuiltinsTest { + + private final UUIDBuiltins builtins = new UUIDBuiltins(); + + @Test + void registersUuidBuiltins() { + assertTrue(BuiltinRegistry.AllBuiltIns.containsKey("uuid.parse")); + assertTrue(BuiltinRegistry.AllBuiltIns.containsKey("uuid.rfc4122")); + } + + @Test + void parsesVersion4Uuid() { + RegoObject result = + (RegoObject) + builtins.parse( + null, new RegoValue[] {new RegoString("00000000-0000-4000-8000-000000000000")}); + + assertEquals(new RegoString("RFC4122"), result.getProperty("variant")); + assertEquals(RegoInt32.of(4), result.getProperty("version")); + } + + @Test + void parsesVersion2UuidMetadata() { + RegoObject result = + (RegoObject) + builtins.parse( + null, new RegoValue[] {new RegoString("000003e8-48b9-21ee-b200-325096b39f47")}); + + assertEquals(RegoInt32.of(12800), result.getProperty("clocksequence")); + assertEquals(new RegoString("Person"), result.getProperty("domain")); + assertEquals(RegoInt32.of(1000), result.getProperty("id")); + assertEquals(new RegoString("local:unicast"), result.getProperty("macvariables")); + assertEquals(new RegoString("32-50-96-b3-9f-47"), result.getProperty("nodeid")); + assertEquals(new RegoBigInt(1693566990121469600L), result.getProperty("time")); + assertEquals(new RegoString("RFC4122"), result.getProperty("variant")); + assertEquals(RegoInt32.of(2), result.getProperty("version")); + } + + @Test + void parsesAcceptedInputFormats() { + assertEquals( + RegoInt32.of(4), + ((RegoObject) + builtins.parse( + null, + new RegoValue[] {new RegoString("{00000000-0000-4000-8000-000000000000}")})) + .getProperty("version")); + assertEquals( + RegoInt32.of(2), + ((RegoObject) + builtins.parse( + null, + new RegoValue[] { + new RegoString("urn:uuid:000003e8-48b9-21ee-b200-325096b39f47") + })) + .getProperty("version")); + assertEquals( + RegoInt32.of(3), + ((RegoObject) + builtins.parse( + null, new RegoValue[] {new RegoString("38074da40b00388d9c3c362de965547a")})) + .getProperty("version")); + } + + @Test + void parseReturnsUndefinedForInvalidUuid() { + assertSame(RegoUndefined.INSTANCE, builtins.parse(null, new RegoValue[] {new RegoString("123")})); + } + + @Test + void rfc4122ReturnsConsistentVersion4UuidForSameKey() { + RegoString first = builtins.rfc4122(null, new RegoValue[] {new RegoString("key")}); + RegoString second = builtins.rfc4122(null, new RegoValue[] {new RegoString("key")}); + UUID parsed = UUID.fromString(first.getValue()); + + assertEquals(first, second); + assertEquals(4, parsed.version()); + assertEquals(2, parsed.variant()); + } +}