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 acc685ec8d4 HDDS-16298. ECBlockChecksumComputer returns empty MD5 due 
to duplicate digest() call (#11125)
acc685ec8d4 is described below

commit acc685ec8d419243153e017a0626ccfa629b4b0a
Author: Mark Tsai <[email protected]>
AuthorDate: Fri Aug 28 23:04:38 2026 +0800

    HDDS-16298. ECBlockChecksumComputer returns empty MD5 due to duplicate 
digest() call (#11125)
    
    Co-authored-by: Wei-Chiu Chuang <[email protected]>
    Co-authored-by: Cursor <[email protected]>
---
 .../client/checksum/ECBlockChecksumComputer.java   |   2 +-
 .../checksum/TestECBlockChecksumComputer.java      | 122 +++++++++++++++++++++
 2 files changed, 123 insertions(+), 1 deletion(-)

diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECBlockChecksumComputer.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECBlockChecksumComputer.java
index 7293c7e29be..7b56d88af07 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECBlockChecksumComputer.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECBlockChecksumComputer.java
@@ -92,7 +92,7 @@ private void computeMd5Crc() {
     }
 
     final byte[] fileMD5 = digester.digest();
-    setOutBytes(digester.digest());
+    setOutBytes(fileMD5);
 
     if (LOG.isDebugEnabled()) {
       LOG.debug("Number of chunks={}, md5hash={}",
diff --git 
a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/TestECBlockChecksumComputer.java
 
b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/TestECBlockChecksumComputer.java
new file mode 100644
index 00000000000..6288a4effae
--- /dev/null
+++ 
b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/TestECBlockChecksumComputer.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.client.checksum;
+
+import static 
org.apache.hadoop.hdds.scm.OzoneClientConfig.ChecksumCombineMode.COMPOSITE_CRC;
+import static 
org.apache.hadoop.hdds.scm.OzoneClientConfig.ChecksumCombineMode.MD5MD5CRC;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.RandomUtils;
+import org.apache.hadoop.hdds.client.ECReplicationConfig;
+import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
+import org.apache.hadoop.io.MD5Hash;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.util.DataChecksum;
+import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for ECBlockChecksumComputer class.
+ */
+public class TestECBlockChecksumComputer {
+
+  private static final ECReplicationConfig EC_CONFIG = new 
ECReplicationConfig(6, 3);
+  private static final long BYTES_PER_CRC = 4L;
+  private static final int CHUNK_LEN = 32;
+
+  @Test
+  public void testComputeMd5Crc() throws IOException {
+    byte[] dataChecksumBytes = RandomUtils.secure().randomBytes(CHUNK_LEN);
+    byte[] expectedMd5 = MD5Hash.digest(dataChecksumBytes).getDigest();
+
+    ECBlockChecksumComputer computer = buildBlockChecksumComputer(
+        dataChecksumBytes, CHUNK_LEN, ContainerProtos.ChecksumType.CRC32);
+    computer.compute(MD5MD5CRC);
+    assertArrayEquals(expectedMd5, computer.getOutByteBuffer().array());
+
+    // Regression guard for HDDS-16298: second MessageDigest.digest() yields 
MD5 of empty input.
+    assertNotEquals(MD5Hash.digest(new byte[0]).getDigest(), expectedMd5);
+  }
+
+  @Test
+  public void testComputeCompositeCrc() throws IOException {
+    byte[] dataChecksumBytes = RandomUtils.secure().randomBytes(CHUNK_LEN);
+    byte[] expectedCompositeCrc = 
computeExpectedCompositeCrc(dataChecksumBytes, CHUNK_LEN);
+
+    ECBlockChecksumComputer computer = buildBlockChecksumComputer(
+        dataChecksumBytes, CHUNK_LEN, ContainerProtos.ChecksumType.CRC32C);
+    computer.compute(COMPOSITE_CRC);
+    assertArrayEquals(expectedCompositeCrc, 
computer.getOutByteBuffer().array());
+  }
+
+  private static byte[] computeExpectedCompositeCrc(byte[] dataChecksumBytes, 
long blockLength)
+      throws IOException {
+    CrcComposer blockCrcComposer =
+        CrcComposer.newCrcComposer(DataChecksum.Type.CRC32C, BYTES_PER_CRC);
+    long blockSize = blockLength;
+    long chunkSize = CHUNK_LEN;
+    ByteBuffer byteWrap = ByteBuffer.wrap(dataChecksumBytes);
+    long remainingChunkSize = Math.min(blockSize, chunkSize);
+    while (byteWrap.hasRemaining() && remainingChunkSize > 0) {
+      final int checksumData = byteWrap.getInt();
+      blockCrcComposer.update(checksumData, Math.min(BYTES_PER_CRC, 
remainingChunkSize));
+      remainingChunkSize -= BYTES_PER_CRC;
+    }
+    return blockCrcComposer.digest();
+  }
+
+  private static ECBlockChecksumComputer buildBlockChecksumComputer(
+      byte[] dataChecksumBytes, int chunkLen, ContainerProtos.ChecksumType 
checksumType) {
+    int parityBytes = getParityBytes(chunkLen, BYTES_PER_CRC, 
EC_CONFIG.getParity());
+    byte[] stripeChecksumBytes = new byte[dataChecksumBytes.length + 
parityBytes];
+    System.arraycopy(dataChecksumBytes, 0, stripeChecksumBytes, 0, 
dataChecksumBytes.length);
+
+    ContainerProtos.ChecksumData checksumData =
+        ContainerProtos.ChecksumData.newBuilder()
+            .setBytesPerChecksum((int) BYTES_PER_CRC)
+            .setType(checksumType)
+            .build();
+    ContainerProtos.ChunkInfo chunkInfo =
+        ContainerProtos.ChunkInfo.newBuilder()
+            .setChecksumData(checksumData)
+            .setChunkName("dummy_chunk")
+            .setOffset(0)
+            .setLen(chunkLen)
+            .setStripeChecksum(ByteString.copyFrom(stripeChecksumBytes))
+            .build();
+    List<ContainerProtos.ChunkInfo> chunkInfoList = 
Collections.singletonList(chunkInfo);
+
+    OmKeyInfo keyInfo = new OmKeyInfo.Builder()
+        .setVolumeName("vol1")
+        .setBucketName("bucket1")
+        .setKeyName("key1")
+        .setReplicationConfig(EC_CONFIG)
+        .build();
+
+    return new ECBlockChecksumComputer(chunkInfoList, keyInfo, chunkLen);
+  }
+
+  private static int getParityBytes(long chunkSize, long bytesPerCrc, int 
numParity) {
+    return (int) (Math.ceil((double) chunkSize / bytesPerCrc) * 4L * 
numParity);
+  }
+}


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

Reply via email to