This is an automated email from the ASF dual-hosted git repository.

adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 3799f87f316 HDDS-15348. OmMultipartPartKeyCodec should not use 
UTF8.decode(..) (#10347)
3799f87f316 is described below

commit 3799f87f3165f97b654e45f6af63be6405ba0629
Author: Russole Chen <[email protected]>
AuthorDate: Sat May 30 18:16:12 2026 +0800

    HDDS-15348. OmMultipartPartKeyCodec should not use UTF8.decode(..) (#10347)
---
 .../ozone/om/helpers/OmMultipartPartKey.java       | 39 +++++++++++++---------
 .../ozone/om/helpers/TestOmMultipartPartKey.java   | 29 +++++++++++++---
 2 files changed, 47 insertions(+), 21 deletions(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmMultipartPartKey.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmMultipartPartKey.java
index 92b71e47190..86fa6fe31e2 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmMultipartPartKey.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmMultipartPartKey.java
@@ -19,10 +19,11 @@
 
 import jakarta.annotation.Nonnull;
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 import java.util.Objects;
 import org.apache.hadoop.hdds.utils.db.Codec;
 import org.apache.hadoop.hdds.utils.db.CodecBuffer;
+import org.apache.hadoop.hdds.utils.db.CodecException;
+import org.apache.hadoop.hdds.utils.db.StringCodec;
 
 /**
  * Typed key for multipart parts table.
@@ -111,8 +112,10 @@ public boolean supportCodecBuffer() {
 
     @Override
     public CodecBuffer toCodecBuffer(
-        @Nonnull OmMultipartPartKey key, CodecBuffer.Allocator allocator) {
-      byte[] uploadBytes = key.uploadId.getBytes(StandardCharsets.UTF_8);
+        @Nonnull OmMultipartPartKey key, CodecBuffer.Allocator allocator)
+        throws CodecException {
+      byte[] uploadBytes = StringCodec.getCodecNoFallback()
+          .toPersistedFormat(key.uploadId);
       int size = uploadBytes.length + 1
           + (key.hasPartNumber() ? Integer.BYTES : 0);
       CodecBuffer buffer = allocator.apply(size);
@@ -125,7 +128,7 @@ public CodecBuffer toCodecBuffer(
 
     @Override
     public OmMultipartPartKey fromCodecBuffer(@Nonnull CodecBuffer buffer)
-        throws IllegalArgumentException {
+        throws CodecException {
       return fromByteBuffer(buffer.asReadOnlyByteBuffer());
     }
 
@@ -138,8 +141,9 @@ public OmMultipartPartKey fromCodecBuffer(@Nonnull 
CodecBuffer buffer)
      * @return Byte array representation of the object for storage in the 
key/value store.
      */
     @Override
-    public byte[] toPersistedFormat(OmMultipartPartKey key) {
-      byte[] uploadBytes = key.uploadId.getBytes(StandardCharsets.UTF_8);
+    public byte[] toPersistedFormat(OmMultipartPartKey key) throws 
CodecException {
+      byte[] uploadBytes = StringCodec.getCodecNoFallback()
+          .toPersistedFormat(key.uploadId);
       int size = uploadBytes.length + 1
           + (key.hasPartNumber() ? Integer.BYTES : 0);
       ByteBuffer buffer = ByteBuffer.allocate(size);
@@ -155,20 +159,20 @@ public byte[] toPersistedFormat(OmMultipartPartKey key) {
      * Decodes the raw byte array from the key/value store into an 
OmMultipartPartKey object.
      * @param rawData Byte array from the key/value store. Should not be null.
      * @return OmMultipartPartKey object represented by the raw byte array.
-     * @throws IllegalArgumentException if the rawData format is invalid
+     * @throws CodecException if the rawData format is invalid
      */
     @Override
-    public OmMultipartPartKey fromPersistedFormat(byte[] rawData) throws 
IllegalArgumentException {
+    public OmMultipartPartKey fromPersistedFormat(byte[] rawData) throws 
CodecException {
       return fromByteBuffer(ByteBuffer.wrap(rawData));
     }
 
     private OmMultipartPartKey fromByteBuffer(ByteBuffer rawData)
-        throws IllegalArgumentException {
+        throws CodecException {
       final ByteBuffer input = rawData.asReadOnlyBuffer();
       final int start = input.position();
       final int length = input.remaining();
       if (length == 0) {
-        throw new IllegalArgumentException(
+        throw new CodecException(
             "Invalid multipart part key: empty key");
       }
 
@@ -178,18 +182,21 @@ private OmMultipartPartKey fromByteBuffer(ByteBuffer 
rawData)
 
       int separatorIndex = start + length - suffixLength - 1;
       if (separatorIndex < start) {
-        throw new IllegalArgumentException(
+        throw new CodecException(
             "Invalid multipart part key: invalid separator position");
       }
       final ByteBuffer uploadIdBuffer = input.duplicate();
       uploadIdBuffer.limit(separatorIndex);
       uploadIdBuffer.position(start);
-      String uploadId = 
StandardCharsets.UTF_8.decode(uploadIdBuffer).toString();
+      byte[] uploadIdBytes = new byte[uploadIdBuffer.remaining()];
+      uploadIdBuffer.get(uploadIdBytes);
+      String uploadId = StringCodec.getCodecNoFallback()
+          .fromPersistedFormat(uploadIdBytes);
       if (suffixLength == 0) {
         return prefix(uploadId);
       }
       if (start + length - (separatorIndex + 1) != Integer.BYTES) {
-        throw new IllegalArgumentException(
+        throw new CodecException(
             "Invalid multipart part key: unexpected part suffix length");
       }
       int part = input.getInt(separatorIndex + 1);
@@ -211,10 +218,10 @@ public OmMultipartPartKey copyObject(OmMultipartPartKey 
object) {
    * @param start the position where key bytes start
    * @param length the number of bytes in the key
    * @return the length of the suffix (0 for prefix keys, Integer.BYTES for 
full keys)
-   * @throws IllegalArgumentException if the key format is invalid (missing 
separator or unexpected suffix length)
+   * @throws CodecException if the key format is invalid (missing separator or 
unexpected suffix length)
    */
   private static int getSuffixLength(ByteBuffer rawData, int start, int length)
-      throws IllegalArgumentException {
+      throws CodecException {
     int suffixLength = -1;
     // Check full-key layout first. Otherwise, part numbers whose low byte is
     // '/' (for example 47 -> 0x0000002f) are mis-classified as prefix keys.
@@ -225,7 +232,7 @@ private static int getSuffixLength(ByteBuffer rawData, int 
start, int length)
       suffixLength = 0;
     }
     if (suffixLength < 0) {
-      throw new IllegalArgumentException(
+      throw new CodecException(
           "Invalid multipart part key: missing separator");
     }
     return suffixLength;
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmMultipartPartKey.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmMultipartPartKey.java
index 309f39a9aa4..2144f1c14b2 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmMultipartPartKey.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestOmMultipartPartKey.java
@@ -26,6 +26,7 @@
 import java.util.stream.IntStream;
 import org.apache.hadoop.hdds.utils.db.Codec;
 import org.apache.hadoop.hdds.utils.db.CodecBuffer;
+import org.apache.hadoop.hdds.utils.db.CodecException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -127,20 +128,27 @@ public void 
testDecodeFullKeyWhenPartLowByteIsSeparator(int partNumber)
 
   @Test
   public void testDecodeRejectsInvalidKeyWithoutSeparator() {
-    assertThrows(IllegalArgumentException.class,
+    assertThrows(CodecException.class,
         () -> codec.fromPersistedFormat("invalid".getBytes(UTF_8)));
   }
 
+  @Test
+  public void testDecodeRejectsMalformedUtf8UploadId() {
+    byte[] malformed = new byte[] {(byte) 0xC3, (byte) '/', 0, 0, 0, 1};
+    assertThrows(CodecException.class,
+        () -> codec.fromPersistedFormat(malformed));
+  }
+
   @Test
   public void testDecodeRejectsEmptyKey() {
-    assertThrows(IllegalArgumentException.class,
+    assertThrows(CodecException.class,
         () -> codec.fromPersistedFormat(new byte[0]));
   }
 
   @Test
   public void testCodecBufferDecodeRejectsInvalidKeyWithoutSeparator() {
     try (CodecBuffer buffer = CodecBuffer.wrap("invalid".getBytes(UTF_8))) {
-      assertThrows(IllegalArgumentException.class,
+      assertThrows(CodecException.class,
           () -> codec.fromCodecBuffer(buffer));
     }
   }
@@ -148,7 +156,7 @@ public void 
testCodecBufferDecodeRejectsInvalidKeyWithoutSeparator() {
   @Test
   public void testCodecBufferDecodeRejectsEmptyKey() {
     try (CodecBuffer buffer = CodecBuffer.wrap(new byte[0])) {
-      assertThrows(IllegalArgumentException.class,
+      assertThrows(CodecException.class,
           () -> codec.fromCodecBuffer(buffer));
     }
   }
@@ -156,7 +164,7 @@ public void testCodecBufferDecodeRejectsEmptyKey() {
   @Test
   public void testDecodeRejectsMalformedKeyWithMiddleSeparatorOnly() {
     byte[] malformed = "up/xx".getBytes(UTF_8);
-    assertThrows(IllegalArgumentException.class,
+    assertThrows(CodecException.class,
         () -> codec.fromPersistedFormat(malformed));
   }
 
@@ -201,4 +209,15 @@ public void testUploadIdContainingSlashRoundTrips() throws 
Exception {
     assertEquals("upload/with/slashes", decoded.getUploadId());
     assertEquals(5, decoded.getPartNumber().intValue());
   }
+
+  @Test
+  public void testEncodeRejectsMalformedUploadId() {
+    OmMultipartPartKey key = OmMultipartPartKey.of("bad-\uD800", 1);
+
+    assertThrows(CodecException.class,
+        () -> codec.toPersistedFormat(key));
+
+    assertThrows(CodecException.class,
+        () -> codec.toHeapCodecBuffer(key));
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to