From 5d4023d1dfaee3a3474dc35ff5ee188abff35770 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 16 Jun 2026 18:30:39 +0800 Subject: [PATCH] [utils]: parse ipv6 system tag tokens Patterned system tags used to split format and tag by ::. IPv6 CIDR tokens like fd66:6:6:6::/64 were split into extra fields, so TagManager rejected valid V2V conversion network tags. Resolves: ZSTAC-85618 Related: ZCF-4134 Test: mvn -pl utils -Dtest=TestTagUtils test Change-Id: I52c6f8f8b3e3473392f55d4d7a497d7b51c87171 --- .../main/java/org/zstack/utils/TagUtils.java | 117 ++++++++++++++++-- .../org/zstack/utils/test/TestTagUtils.java | 30 +++++ 2 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 utils/src/test/java/org/zstack/utils/test/TestTagUtils.java diff --git a/utils/src/main/java/org/zstack/utils/TagUtils.java b/utils/src/main/java/org/zstack/utils/TagUtils.java index 823a7af6dc2..33e41aab68f 100755 --- a/utils/src/main/java/org/zstack/utils/TagUtils.java +++ b/utils/src/main/java/org/zstack/utils/TagUtils.java @@ -8,14 +8,20 @@ /** */ public class TagUtils { - public static Map parse(String fmt, String tag) { - List origins = new ArrayList(); - Collections.addAll(origins, tag.split("::")); + private static final String TAG_DELIMITER = "::"; + private static final char TOKEN_START = '{'; + private static final char TOKEN_END = '}'; + public static Map parse(String fmt, String tag) { List t = new ArrayList(); - Collections.addAll(t, fmt.split("::")); + t.addAll(splitTagFields(fmt)); + List origins = splitTagFieldsByFormat(t, tag); Map ret = new HashMap(); + if (origins == null) { + return ret; + } + for (int i=0;i origins = new ArrayList(); - Collections.addAll(origins, tag.split("::")); - List t = new ArrayList(); - Collections.addAll(t, fmt.split("::")); + t.addAll(splitTagFields(fmt)); - if (fmt.indexOf("::") == -1) { + if (fmt.indexOf(TAG_DELIMITER) == -1) { return fmt.equals(tag); } - if (origins.size() != t.size()) { + List origins = splitTagFieldsByFormat(t, tag); + if (origins == null || origins.size() != t.size()) { return false; } @@ -66,6 +70,99 @@ public static boolean isMatch(String fmt, String tag) { return true; } + private static List splitTagFieldsByFormat(List fmtFields, String tag) { + List fields = new ArrayList<>(); + int offset = 0; + + for (int i = 0; i < fmtFields.size(); i++) { + String fmtField = fmtFields.get(i); + boolean lastField = i == fmtFields.size() - 1; + if (isTokenField(fmtField)) { + if (lastField) { + fields.add(tag.substring(offset)); + offset = tag.length(); + continue; + } + + int end = indexOfDelimiterOutsideToken(tag, offset); + if (end < 0) { + return null; + } + fields.add(tag.substring(offset, end)); + offset = end + TAG_DELIMITER.length(); + continue; + } + + if (!tag.startsWith(fmtField, offset)) { + return null; + } + + fields.add(fmtField); + offset += fmtField.length(); + if (!lastField) { + if (!tag.startsWith(TAG_DELIMITER, offset)) { + return null; + } + offset += TAG_DELIMITER.length(); + } + } + + return offset == tag.length() ? fields : null; + } + + private static boolean isTokenField(String field) { + return field.startsWith(String.valueOf(TOKEN_START)) && field.endsWith(String.valueOf(TOKEN_END)); + } + + private static List splitTagFields(String tag) { + List fields = new ArrayList<>(); + StringBuilder field = new StringBuilder(); + int braceDepth = 0; + + for (int i = 0; i < tag.length(); i++) { + char current = tag.charAt(i); + if (current == TOKEN_START) { + braceDepth++; + } else if (current == TOKEN_END && braceDepth > 0) { + braceDepth--; + } + + if (braceDepth == 0 && tag.startsWith(TAG_DELIMITER, i)) { + fields.add(field.toString()); + field.setLength(0); + i += TAG_DELIMITER.length() - 1; + continue; + } + + field.append(current); + } + + fields.add(field.toString()); + while (!fields.isEmpty() && fields.get(fields.size() - 1).isEmpty()) { + fields.remove(fields.size() - 1); + } + + return fields; + } + + private static int indexOfDelimiterOutsideToken(String tag, int offset) { + int braceDepth = 0; + for (int i = offset; i < tag.length(); i++) { + char current = tag.charAt(i); + if (current == TOKEN_START) { + braceDepth++; + } else if (current == TOKEN_END && braceDepth > 0) { + braceDepth--; + } + + if (braceDepth == 0 && tag.startsWith(TAG_DELIMITER, i)) { + return i; + } + } + + return -1; + } + public static Map parseIfMatch(String fmt, String tag) { if (!isMatch(fmt, tag)) { return null; diff --git a/utils/src/test/java/org/zstack/utils/test/TestTagUtils.java b/utils/src/test/java/org/zstack/utils/test/TestTagUtils.java new file mode 100644 index 00000000000..8b3e57cd897 --- /dev/null +++ b/utils/src/test/java/org/zstack/utils/test/TestTagUtils.java @@ -0,0 +1,30 @@ +package org.zstack.utils.test; + +import org.junit.Assert; +import org.junit.Test; +import org.zstack.utils.TagUtils; + +import java.util.Map; + +public class TestTagUtils { + @Test + public void testIpv6CidrTokenAtEnd() { + String format = "conversion::network::cidr::{conversionNetwork}"; + String tag = "conversion::network::cidr::fd66:6:6:6::/64"; + + Assert.assertTrue(TagUtils.isMatch(format, tag)); + Map tokens = TagUtils.parseIfMatch(format, tag); + Assert.assertEquals("fd66:6:6:6::/64", tokens.get("conversionNetwork")); + } + + @Test + public void testBracedIpv6TokenBeforeStaticField() { + String format = "resource::{cidr}::state::{state}"; + String tag = "resource::{fd66:6:6:6::/64}::state::enabled"; + + Assert.assertTrue(TagUtils.isMatch(format, tag)); + Map tokens = TagUtils.parseIfMatch(format, tag); + Assert.assertEquals("{fd66:6:6:6::/64}", tokens.get("cidr")); + Assert.assertEquals("enabled", tokens.get("state")); + } +}