You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by yc...@apache.org on 2024/03/11 02:12:07 UTC

(cassandra-sidecar) branch trunk updated: CASSANDRASC-114 Make hash algorithm implementation pluggable

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

ycai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git


The following commit(s) were added to refs/heads/trunk by this push:
     new ca1de4c  CASSANDRASC-114 Make hash algorithm implementation pluggable
ca1de4c is described below

commit ca1de4cc2e594266c1fca9f83cfcac38d9a21c45
Author: Yifan Cai <yc...@apache.org>
AuthorDate: Fri Mar 8 12:17:38 2024 -0800

    CASSANDRASC-114 Make hash algorithm implementation pluggable
    
    patch by Yifan Cai; reviewed by Francisco Guerrero for CASSANDRASC-114
---
 CHANGES.txt                                        |   1 +
 build.gradle                                       |   4 +-
 gradle.properties                                  |   3 +-
 .../apache/cassandra/sidecar/db/RestoreSlice.java  |   7 +-
 .../cassandra/sidecar/restore/RestoreJobUtil.java  | 140 +++++++++++----------
 .../sidecar/restore/RestoreProcessor.java          |   9 +-
 .../sidecar/restore/RestoreSliceTask.java          |   7 +-
 .../cassandra/sidecar/server/MainModule.java       |  22 ++++
 .../sidecar/utils/AsyncFileDigestVerifier.java     |  30 ++++-
 .../cassandra/sidecar/utils/DigestAlgorithm.java   |  41 ++++++
 .../sidecar/utils/DigestAlgorithmProvider.java     |  39 ++++++
 .../sidecar/utils/DigestVerifierFactory.java       |  18 ++-
 .../sidecar/utils/JdkMd5DigestProvider.java        |  74 +++++++++++
 .../cassandra/sidecar/utils/MD5DigestVerifier.java |  41 ++----
 .../sidecar/utils/XXHash32DigestVerifier.java      |  46 ++-----
 .../cassandra/sidecar/utils/XXHash32Provider.java  |  64 ++++++++++
 .../sidecar/restore/RestoreJobUtilTest.java        |  10 +-
 .../sidecar/restore/RestoreProcessorTest.java      |   2 +-
 .../sidecar/restore/RestoreSliceTaskTest.java      |   3 +-
 .../sstableuploads/SSTableUploadHandlerTest.java   |  11 +-
 .../sidecar/utils/DigestVerifierFactoryTest.java   |  14 ++-
 .../sidecar/utils/MD5DigestVerifierTest.java       |   2 +-
 .../sidecar/utils/XXHash32DigestVerifierTest.java  |  20 ++-
 23 files changed, 431 insertions(+), 177 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 351ca44..0fdc914 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * Make hash algorithm implementation pluggable (CASSANDRASC-114)
  * Fix ClosedChannelException when downloading from S3 (CASSANDRASC-112)
  * Fix NPE thrown when getting StorageClient from pool (CASSANDRASC-110)
  * Relocate Sidecar common classes in vertx-client-shaded (CASSANDRASC-104)
diff --git a/build.gradle b/build.gradle
index 5aa49d5..2db4461 100644
--- a/build.gradle
+++ b/build.gradle
@@ -197,7 +197,9 @@ dependencies {
     implementation('ch.qos.logback:logback-core:1.2.3')
     implementation('ch.qos.logback:logback-classic:1.2.3')
 
+
     implementation(group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0')
+    implementation(group: 'commons-codec', name: 'commons-codec', version: "${project.commonsCodecVersion}")
 
     // Jackson for yaml-based configuration parsing
     implementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${project.jacksonVersion}")
@@ -208,8 +210,6 @@ dependencies {
     implementation('software.amazon.awssdk:s3')
     implementation('software.amazon.awssdk:netty-nio-client')
 
-    implementation(group: 'org.lz4', name: 'lz4-java', version: "${project.xxHashVersion}")
-
     jolokia 'org.jolokia:jolokia-jvm:1.6.0:agent'
 
     testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junitVersion}"
diff --git a/gradle.properties b/gradle.properties
index 29f567c..98068d7 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -27,4 +27,5 @@ dtestApiVersion=0.0.16
 dtestVersion=5.1
 dtestDependencyName=cassandra-dtest-local-all
 aswSdkVersion=2.20.43
-xxHashVersion=1.8.0
+# The dep is to introduce xxhash impl
+commonsCodecVersion=1.16.1
diff --git a/src/main/java/org/apache/cassandra/sidecar/db/RestoreSlice.java b/src/main/java/org/apache/cassandra/sidecar/db/RestoreSlice.java
index 6353894..7563e93 100644
--- a/src/main/java/org/apache/cassandra/sidecar/db/RestoreSlice.java
+++ b/src/main/java/org/apache/cassandra/sidecar/db/RestoreSlice.java
@@ -39,6 +39,7 @@ import org.apache.cassandra.sidecar.common.data.RestoreSliceStatus;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 import org.apache.cassandra.sidecar.exceptions.RestoreJobExceptions;
 import org.apache.cassandra.sidecar.exceptions.RestoreJobFatalException;
+import org.apache.cassandra.sidecar.restore.RestoreJobUtil;
 import org.apache.cassandra.sidecar.restore.RestoreSliceTask;
 import org.apache.cassandra.sidecar.restore.RestoreSliceTracker;
 import org.apache.cassandra.sidecar.restore.StorageClient;
@@ -233,7 +234,8 @@ public class RestoreSlice
                                                       SSTableImporter importer,
                                                       double requiredUsableSpacePercentage,
                                                       RestoreSliceDatabaseAccessor sliceDatabaseAccessor,
-                                                      RestoreJobStats stats)
+                                                      RestoreJobStats stats,
+                                                      RestoreJobUtil restoreJobUtil)
     {
         if (isCancelled)
             return promise -> promise.tryFail(RestoreJobExceptions.ofFatalSlice("Restore slice is cancelled",
@@ -246,7 +248,8 @@ public class RestoreSlice
                                         executorPool, importer,
                                         requiredUsableSpacePercentage,
                                         sliceDatabaseAccessor,
-                                        stats);
+                                        stats,
+                                        restoreJobUtil);
         }
         catch (IllegalStateException illegalState)
         {
diff --git a/src/main/java/org/apache/cassandra/sidecar/restore/RestoreJobUtil.java b/src/main/java/org/apache/cassandra/sidecar/restore/RestoreJobUtil.java
index d33ff37..ef36316 100644
--- a/src/main/java/org/apache/cassandra/sidecar/restore/RestoreJobUtil.java
+++ b/src/main/java/org/apache/cassandra/sidecar/restore/RestoreJobUtil.java
@@ -19,8 +19,8 @@
 package org.apache.cassandra.sidecar.restore;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
@@ -34,23 +34,38 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.datastax.driver.core.utils.UUIDs;
-import net.jpountz.xxhash.StreamingXXHash32;
-import net.jpountz.xxhash.XXHashFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
 import org.apache.cassandra.sidecar.exceptions.RestoreJobException;
 import org.apache.cassandra.sidecar.exceptions.RestoreJobFatalException;
+import org.apache.cassandra.sidecar.utils.DigestAlgorithm;
+import org.apache.cassandra.sidecar.utils.DigestAlgorithmProvider;
 
 /**
- * Utilities that only makes sense in the context of restore jobs. Avoid using it in the other scenarios.
+ * Utilities that only makes sense in the context of restore jobs.
+ *
+ * Note: Avoid using it in the other scenarios.
+ *
  */
+@Singleton
 public class RestoreJobUtil
 {
     private static final Logger LOGGER = LoggerFactory.getLogger(RestoreJobUtil.class);
-    private RestoreJobUtil() {}
     private static final int KB_512 = 512 * 1024;
     // it is part of upload id and get validated by
     // org.apache.cassandra.sidecar.utils.SSTableUploadsPathBuilder.UPLOAD_ID_PATTERN
     private static final String RESTORE_JOB_PREFIX = "c0ffee-";
     private static final int RESTORE_JOB_PREFIX_LEN = RESTORE_JOB_PREFIX.length();
+    private static final int RESTORE_JOB_DEFAULT_HASH_SEED = 0;
+
+    private DigestAlgorithmProvider digestAlgorithmProvider;
+
+    @Inject
+    public RestoreJobUtil(@Named("xxhash32") DigestAlgorithmProvider digestAlgorithmProvider)
+    {
+        this.digestAlgorithmProvider = digestAlgorithmProvider;
+    }
 
     /**
      * Unzip a restore slice zip
@@ -84,65 +99,6 @@ public class RestoreJobUtil
         }
     }
 
-    /**
-     * Create a file that is protected from zip slip attack (https://security.snyk.io/research/zip-slip-vulnerability)
-     * @param zipEntry zip entry to be extracted
-     * @param targetDir directory to keep the unzipped files
-     * @return a new file
-     * @throws IOException failed to resolving path
-     * @throws RestoreJobException if the zip file is malicious
-     */
-    private static File newProtectedTargetFile(ZipEntry zipEntry, File targetDir)
-    throws IOException, RestoreJobException
-    {
-        File targetFile = new File(targetDir, zipEntry.getName());
-
-        // Normalize the paths of both target dir and file
-        String targetDirPath = targetDir.getCanonicalPath();
-        String targetFilePath = targetFile.getCanonicalPath();
-
-        if (!targetFilePath.startsWith(targetDirPath))
-        {
-            throw new RestoreJobException("Bad zip entry: " + zipEntry.getName());
-        }
-
-        return targetFile;
-    }
-
-    /**
-     * @param file the file to use to perform the checksum
-     * @return the checksum hex string of the file's content. XXHash32 is employed as the hash algorithm.
-     */
-    public static String checksum(File file) throws IOException
-    {
-        int seed = 0x9747b28c; // random seed for initializing
-        return checksum(file, seed);
-    }
-
-    /**
-     * @param file the file to use to perform the checksum
-     * @param seed the seed to use for the hasher
-     * @return the checksum hex string of the file's content. XXHash32 is employed as the hash algorithm.
-     */
-    public static String checksum(File file, int seed) throws IOException
-    {
-        try (FileInputStream fis = new FileInputStream(file))
-        {
-            // might have shared hashers with ThreadLocal
-            XXHashFactory factory = XXHashFactory.safeInstance();
-            try (StreamingXXHash32 hasher = factory.newStreamingHash32(seed))
-            {
-                byte[] buffer = new byte[KB_512];
-                int len;
-                while ((len = fis.read(buffer)) != -1)
-                {
-                    hasher.update(buffer, 0, len);
-                }
-                return Long.toHexString(hasher.getValue());
-            }
-        }
-    }
-
     /**
      * Resolve a prefixed job id for path
      * @param jobId restore job id
@@ -211,4 +167,60 @@ public class RestoreJobUtil
             });
         }
     }
+
+    /**
+     * Create a file that is protected from zip slip attack (https://security.snyk.io/research/zip-slip-vulnerability)
+     * @param zipEntry zip entry to be extracted
+     * @param targetDir directory to keep the unzipped files
+     * @return a new file
+     * @throws IOException failed to resolving path
+     * @throws RestoreJobException if the zip file is malicious
+     */
+    private static File newProtectedTargetFile(ZipEntry zipEntry, File targetDir)
+    throws IOException, RestoreJobException
+    {
+        File targetFile = new File(targetDir, zipEntry.getName());
+
+        // Normalize the paths of both target dir and file
+        String targetDirPath = targetDir.getCanonicalPath();
+        String targetFilePath = targetFile.getCanonicalPath();
+
+        if (!targetFilePath.startsWith(targetDirPath))
+        {
+            throw new RestoreJobException("Bad zip entry: " + zipEntry.getName());
+        }
+
+        return targetFile;
+    }
+
+    /**
+     * @param file the file to use to perform the checksum
+     * @return the checksum hex string of the file's content. XXHash32 is employed as the hash algorithm.
+     */
+    public String checksum(File file) throws IOException
+    {
+        return checksum(file, RESTORE_JOB_DEFAULT_HASH_SEED);
+    }
+
+    /**
+     * @param file the file to use to perform the checksum
+     * @param seed the seed to use for the hasher
+     * @return the checksum hex string of the file's content. XXHash32 is employed as the hash algorithm.
+     */
+    public String checksum(File file, int seed) throws IOException
+    {
+        try (InputStream fis = Files.newInputStream(file.toPath()))
+        {
+            try (DigestAlgorithm digestAlgorithm = digestAlgorithmProvider.get(seed))
+            {
+                byte[] buffer = new byte[KB_512];
+                int len;
+                while ((len = fis.read(buffer)) != -1)
+                {
+                    digestAlgorithm.update(buffer, 0, len);
+                }
+                return digestAlgorithm.digest();
+            }
+        }
+    }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/restore/RestoreProcessor.java b/src/main/java/org/apache/cassandra/sidecar/restore/RestoreProcessor.java
index af6332f..1a438dc 100644
--- a/src/main/java/org/apache/cassandra/sidecar/restore/RestoreProcessor.java
+++ b/src/main/java/org/apache/cassandra/sidecar/restore/RestoreProcessor.java
@@ -61,6 +61,8 @@ public class RestoreProcessor implements PeriodicTask
     private final double requiredUsableSpacePercentage; // value range: [0.0, 1.0)
     private final RestoreSliceDatabaseAccessor sliceDatabaseAccessor;
     private final RestoreJobStats stats;
+    private final RestoreJobUtil restoreJobUtil;
+
     private volatile boolean isClosed = false; // OK to run close twice, so relax the control to volatile
 
     @Inject
@@ -70,7 +72,8 @@ public class RestoreProcessor implements PeriodicTask
                             StorageClientPool s3ClientPool,
                             SSTableImporter importer,
                             RestoreSliceDatabaseAccessor sliceDatabaseAccessor,
-                            RestoreJobStats stats)
+                            RestoreJobStats stats,
+                            RestoreJobUtil restoreJobUtil)
     {
         this.pool = executorPools.internal();
         this.s3ClientPool = s3ClientPool;
@@ -82,6 +85,7 @@ public class RestoreProcessor implements PeriodicTask
         this.importer = importer;
         this.sliceDatabaseAccessor = sliceDatabaseAccessor;
         this.stats = stats;
+        this.restoreJobUtil = restoreJobUtil;
     }
 
     /**
@@ -131,7 +135,8 @@ public class RestoreProcessor implements PeriodicTask
             sliceQueue.captureImportQueueLength();
             pool.executeBlocking(slice.toAsyncTask(s3ClientPool, pool, importer,
                                                    requiredUsableSpacePercentage,
-                                                   sliceDatabaseAccessor, stats),
+                                                   sliceDatabaseAccessor, stats,
+                                                   restoreJobUtil),
                                  false) // unordered
             .onSuccess(restoreSlice -> {
                 if (slice.hasImported())
diff --git a/src/main/java/org/apache/cassandra/sidecar/restore/RestoreSliceTask.java b/src/main/java/org/apache/cassandra/sidecar/restore/RestoreSliceTask.java
index 0cec6f2..ecd0734 100644
--- a/src/main/java/org/apache/cassandra/sidecar/restore/RestoreSliceTask.java
+++ b/src/main/java/org/apache/cassandra/sidecar/restore/RestoreSliceTask.java
@@ -70,6 +70,7 @@ public class RestoreSliceTask implements Handler<Promise<RestoreSlice>>
     private final double requiredUsableSpacePercentage;
     private final RestoreSliceDatabaseAccessor sliceDatabaseAccessor;
     private final RestoreJobStats stats;
+    private final RestoreJobUtil restoreJobUtil;
 
     public RestoreSliceTask(RestoreSlice slice,
                             StorageClient s3Client,
@@ -77,7 +78,8 @@ public class RestoreSliceTask implements Handler<Promise<RestoreSlice>>
                             SSTableImporter importer,
                             double requiredUsableSpacePercentage,
                             RestoreSliceDatabaseAccessor sliceDatabaseAccessor,
-                            RestoreJobStats stats)
+                            RestoreJobStats stats,
+                            RestoreJobUtil restoreJobUtil)
     {
         Preconditions.checkArgument(!slice.job().isManagedBySidecar()
                                     || sliceDatabaseAccessor != null,
@@ -89,6 +91,7 @@ public class RestoreSliceTask implements Handler<Promise<RestoreSlice>>
         this.requiredUsableSpacePercentage = requiredUsableSpacePercentage;
         this.sliceDatabaseAccessor = sliceDatabaseAccessor;
         this.stats = stats;
+        this.restoreJobUtil = restoreJobUtil;
     }
 
     @Override
@@ -442,7 +445,7 @@ public class RestoreSliceTask implements Handler<Promise<RestoreSlice>>
 
             try
             {
-                String actualChecksum = RestoreJobUtil.checksum(file);
+                String actualChecksum = restoreJobUtil.checksum(file);
                 if (!actualChecksum.equals(expectedChecksum))
                 {
                     String msg = "Checksum does not match. Expected: " + expectedChecksum +
diff --git a/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java
index 008fadc..a85dbb6 100644
--- a/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java
@@ -98,7 +98,10 @@ import org.apache.cassandra.sidecar.stats.RestoreJobStats;
 import org.apache.cassandra.sidecar.stats.SidecarSchemaStats;
 import org.apache.cassandra.sidecar.stats.SidecarStats;
 import org.apache.cassandra.sidecar.utils.CassandraVersionProvider;
+import org.apache.cassandra.sidecar.utils.DigestAlgorithmProvider;
+import org.apache.cassandra.sidecar.utils.JdkMd5DigestProvider;
 import org.apache.cassandra.sidecar.utils.TimeProvider;
+import org.apache.cassandra.sidecar.utils.XXHash32Provider;
 
 import static org.apache.cassandra.sidecar.common.utils.ByteUtils.bytesToHumanReadableBinaryPrefix;
 import static org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SERVER_STOP;
@@ -507,6 +510,25 @@ public class MainModule extends AbstractModule
                                  stats, sidecarInternalKeyspace, cqlSessionProvider);
     }
 
+    /**
+     * The provided hasher is used in {@link org.apache.cassandra.sidecar.restore.RestoreJobUtil}
+     */
+    @Provides
+    @Singleton
+    @Named("xxhash32")
+    public DigestAlgorithmProvider xxHash32Provider()
+    {
+        return new XXHash32Provider();
+    }
+
+    @Provides
+    @Singleton
+    @Named("md5")
+    public DigestAlgorithmProvider md5Provider()
+    {
+        return new JdkMd5DigestProvider();
+    }
+
     /**
      * Builds the {@link InstanceMetadata} from the {@link InstanceConfiguration},
      * a provided {@code  versionProvider}, and {@code healthCheckFrequencyMillis}.
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/AsyncFileDigestVerifier.java b/src/main/java/org/apache/cassandra/sidecar/utils/AsyncFileDigestVerifier.java
index 790b54c..0174e12 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/AsyncFileDigestVerifier.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/AsyncFileDigestVerifier.java
@@ -18,6 +18,7 @@
 
 package org.apache.cassandra.sidecar.utils;
 
+import java.io.IOException;
 import java.util.Objects;
 
 import org.slf4j.Logger;
@@ -46,11 +47,13 @@ public abstract class AsyncFileDigestVerifier<D extends Digest> implements Diges
     protected final Logger logger = LoggerFactory.getLogger(this.getClass());
     protected final FileSystem fs;
     protected final D digest;
+    private final DigestAlgorithm digestAlgorithm;
 
-    protected AsyncFileDigestVerifier(FileSystem fs, D digest)
+    protected AsyncFileDigestVerifier(FileSystem fs, D digest, DigestAlgorithm digestAlgorithm)
     {
         this.fs = fs;
         this.digest = Objects.requireNonNull(digest, "digest is required");
+        this.digestAlgorithm = digestAlgorithm;
     }
 
     /**
@@ -86,7 +89,30 @@ public abstract class AsyncFileDigestVerifier<D extends Digest> implements Diges
      * @param asyncFile the async file to use for digest calculation
      * @return a future with the computed digest for the provided {@link AsyncFile file}
      */
-    protected abstract Future<String> calculateDigest(AsyncFile asyncFile);
+    protected Future<String> calculateDigest(AsyncFile asyncFile)
+    {
+        Promise<String> result = Promise.promise();
+
+        readFile(asyncFile, result,
+                 buf -> {
+                     byte[] bytes = buf.getBytes();
+                     digestAlgorithm.update(bytes, 0, bytes.length);
+                 },
+                 onReadComplete -> {
+                     result.complete(digestAlgorithm.digest());
+                     try
+                     {
+                         digestAlgorithm.close();
+                     }
+                     catch (IOException e)
+                     {
+                         logger.warn("Potential memory leak due to failed to close hasher {}",
+                                     digestAlgorithm.getClass().getSimpleName());
+                     }
+                 });
+
+        return result.future();
+    }
 
     protected void readFile(AsyncFile file, Promise<String> result, Handler<Buffer> onBufferAvailable,
                             Handler<Void> onReadComplete)
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/DigestAlgorithm.java b/src/main/java/org/apache/cassandra/sidecar/utils/DigestAlgorithm.java
new file mode 100644
index 0000000..415405c
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/DigestAlgorithm.java
@@ -0,0 +1,41 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+import java.io.Closeable;
+
+/**
+ * Abstraction for digest algorithms
+ */
+public interface DigestAlgorithm extends Closeable
+{
+    /**
+     * Updates the value of the digest with buf[off:off+len].
+     *
+     * @param buf the input data
+     * @param off the start offset in buf
+     * @param len the number of bytes to hash
+     */
+    void update(byte[] buf, int off, int len);
+
+    /**
+     * @return String value of the digest
+     */
+    String digest();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/DigestAlgorithmProvider.java b/src/main/java/org/apache/cassandra/sidecar/utils/DigestAlgorithmProvider.java
new file mode 100644
index 0000000..5a01355
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/DigestAlgorithmProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+/**
+ * Provides a digest algorithm implementation
+ */
+public interface DigestAlgorithmProvider
+{
+    /**
+     * @return a new digest algorithm instance with default seed 0
+     */
+    default DigestAlgorithm get()
+    {
+        return get(0);
+    }
+
+    /**
+     * @param seed seed for digest algorithm
+     * @return a new digest algorithm instance with the provided seed
+     */
+    DigestAlgorithm get(int seed);
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactory.java b/src/main/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactory.java
index 3dd64a2..43734d7 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactory.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactory.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.sidecar.utils;
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.google.inject.name.Named;
 import io.netty.handler.codec.http.HttpHeaderNames;
 import io.vertx.core.Future;
 import io.vertx.core.MultiMap;
@@ -38,17 +39,24 @@ public class DigestVerifierFactory
     @VisibleForTesting
     static final DigestVerifier FALLBACK_VERIFIER = Future::succeededFuture;
     private final FileSystem fs;
-
+    private final DigestAlgorithmProvider xxhash32;
+    private final DigestAlgorithmProvider md5;
 
     /**
-     * Constructs a new factory
+     * Constructs a new factory with a list of hash algorithms it supports
      *
      * @param vertx the vertx instance
+     * @param xxhash32 xxhash32 hash algorithm it supports
+     * @param md5 md5 hash algorithm it supports
      */
     @Inject
-    public DigestVerifierFactory(Vertx vertx)
+    public DigestVerifierFactory(Vertx vertx,
+                                 @Named("xxhash32") DigestAlgorithmProvider xxhash32,
+                                 @Named("md5") DigestAlgorithmProvider md5)
     {
         this.fs = vertx.fileSystem();
+        this.xxhash32 = xxhash32;
+        this.md5 = md5;
     }
 
     /***
@@ -64,11 +72,11 @@ public class DigestVerifierFactory
     {
         if (headers.contains(CONTENT_XXHASH32))
         {
-            return XXHash32DigestVerifier.create(fs, headers);
+            return XXHash32DigestVerifier.create(fs, headers, xxhash32);
         }
         else if (headers.contains(HttpHeaderNames.CONTENT_MD5.toString()))
         {
-            return MD5DigestVerifier.create(fs, headers);
+            return MD5DigestVerifier.create(fs, headers, md5);
         }
         // Fallback to no-op validator
         return FALLBACK_VERIFIER;
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/JdkMd5DigestProvider.java b/src/main/java/org/apache/cassandra/sidecar/utils/JdkMd5DigestProvider.java
new file mode 100644
index 0000000..6f213cb
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/JdkMd5DigestProvider.java
@@ -0,0 +1,74 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+/**
+ * Provides the MD5 implementation from JDK
+ */
+public class JdkMd5DigestProvider implements DigestAlgorithmProvider
+{
+    @Override
+    public DigestAlgorithm get(int seed)
+    {
+        return new JdkMD5Digest();
+    }
+
+    /**
+     * MD5 implementation from JDK
+     */
+    public static class JdkMD5Digest implements DigestAlgorithm
+    {
+        private final MessageDigest md5;
+
+        public JdkMD5Digest()
+        {
+            try
+            {
+                this.md5 = MessageDigest.getInstance("MD5");
+            }
+            catch (NoSuchAlgorithmException ex)
+            {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void update(byte[] buf, int off, int len)
+        {
+            md5.update(buf, off, len);
+        }
+
+        @Override
+        public String digest()
+        {
+            return Base64.getEncoder().encodeToString(md5.digest());
+        }
+
+        @Override
+        public void close()
+        {
+            // MessageDigest does not require to be closed
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifier.java b/src/main/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifier.java
index ed4218d..7b80b8c 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifier.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifier.java
@@ -18,18 +18,11 @@
 
 package org.apache.cassandra.sidecar.utils;
 
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Base64;
-
 import io.netty.handler.codec.http.HttpHeaderNames;
-import io.vertx.core.Future;
 import io.vertx.core.MultiMap;
-import io.vertx.core.Promise;
-import io.vertx.core.file.AsyncFile;
 import io.vertx.core.file.FileSystem;
 import org.apache.cassandra.sidecar.common.data.MD5Digest;
-import org.jetbrains.annotations.VisibleForTesting;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Implementation of {@link DigestVerifier}. Here we use MD5 implementation of {@link java.security.MessageDigest}
@@ -37,36 +30,16 @@ import org.jetbrains.annotations.VisibleForTesting;
  */
 public class MD5DigestVerifier extends AsyncFileDigestVerifier<MD5Digest>
 {
-    protected MD5DigestVerifier(FileSystem fs, MD5Digest digest)
+    protected MD5DigestVerifier(@NotNull FileSystem fs, @NotNull MD5Digest digest,
+                                @NotNull DigestAlgorithm digestAlgorithm)
     {
-        super(fs, digest);
+        super(fs, digest, digestAlgorithm);
     }
 
-    public static DigestVerifier create(FileSystem fs, MultiMap headers)
+    public static DigestVerifier create(FileSystem fs, MultiMap headers,
+                                        DigestAlgorithmProvider digestAlgorithmProvider)
     {
         MD5Digest md5Digest = new MD5Digest(headers.get(HttpHeaderNames.CONTENT_MD5.toString()));
-        return new MD5DigestVerifier(fs, md5Digest);
-    }
-
-    @Override
-    @VisibleForTesting
-    protected Future<String> calculateDigest(AsyncFile file)
-    {
-        MessageDigest digest;
-        try
-        {
-            digest = MessageDigest.getInstance("MD5");
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            return Future.failedFuture(e);
-        }
-
-        Promise<String> result = Promise.promise();
-
-        readFile(file, result, buf -> digest.update(buf.getBytes()),
-                 _v -> result.complete(Base64.getEncoder().encodeToString(digest.digest())));
-
-        return result.future();
+        return new MD5DigestVerifier(fs, md5Digest, digestAlgorithmProvider.get());
     }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifier.java b/src/main/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifier.java
index b1944c6..14347ce 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifier.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifier.java
@@ -18,15 +18,10 @@
 
 package org.apache.cassandra.sidecar.utils;
 
-import io.vertx.core.Future;
 import io.vertx.core.MultiMap;
-import io.vertx.core.Promise;
-import io.vertx.core.file.AsyncFile;
 import io.vertx.core.file.FileSystem;
-import net.jpountz.xxhash.StreamingXXHash32;
-import net.jpountz.xxhash.XXHashFactory;
 import org.apache.cassandra.sidecar.common.data.XXHash32Digest;
-import org.jetbrains.annotations.VisibleForTesting;
+import org.jetbrains.annotations.NotNull;
 
 import static org.apache.cassandra.sidecar.common.http.SidecarHttpHeaderNames.CONTENT_XXHASH32;
 import static org.apache.cassandra.sidecar.common.http.SidecarHttpHeaderNames.CONTENT_XXHASH32_SEED;
@@ -37,48 +32,27 @@ import static org.apache.cassandra.sidecar.common.http.SidecarHttpHeaderNames.CO
  */
 public class XXHash32DigestVerifier extends AsyncFileDigestVerifier<XXHash32Digest>
 {
-    protected XXHash32DigestVerifier(FileSystem fs, XXHash32Digest digest)
+    protected XXHash32DigestVerifier(@NotNull FileSystem fs, @NotNull XXHash32Digest digest,
+                                     @NotNull DigestAlgorithm digestAlgorithm)
     {
-        super(fs, digest);
+        super(fs, digest, digestAlgorithm);
     }
 
-    public static XXHash32DigestVerifier create(FileSystem fs, MultiMap headers)
+    public static XXHash32DigestVerifier create(FileSystem fs, MultiMap headers,
+                                                DigestAlgorithmProvider digestAlgorithmProvider)
     {
         XXHash32Digest digest = new XXHash32Digest(headers.get(CONTENT_XXHASH32), headers.get(CONTENT_XXHASH32_SEED));
-        return new XXHash32DigestVerifier(fs, digest);
+        DigestAlgorithm digestAlgorithm = digestAlgorithmProvider.get(maybeGetSeedOrDefault(digest));
+        return new XXHash32DigestVerifier(fs, digest, digestAlgorithm);
     }
 
-    @Override
-    @VisibleForTesting
-    protected Future<String> calculateDigest(AsyncFile file)
-    {
-        Promise<String> result = Promise.promise();
-        Future<String> future = result.future();
-
-        // might have shared hashers with ThreadLocal
-        XXHashFactory factory = XXHashFactory.safeInstance();
-
-        int seed = maybeGetSeedOrDefault();
-        StreamingXXHash32 hasher = factory.newStreamingHash32(seed);
-
-        future.onComplete(ignored -> hasher.close());
-
-        readFile(file, result, buf -> {
-                     byte[] bytes = buf.getBytes();
-                     hasher.update(bytes, 0, bytes.length);
-                 },
-                 _v -> result.complete(Long.toHexString(hasher.getValue())));
-
-        return future;
-    }
-
-    protected int maybeGetSeedOrDefault()
+    protected static int maybeGetSeedOrDefault(XXHash32Digest digest)
     {
         String seedHex = digest.seedHex();
         if (seedHex != null)
         {
             return (int) Long.parseLong(seedHex, 16);
         }
-        return 0x9747b28c; // random seed for initializing
+        return 0;
     }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/XXHash32Provider.java b/src/main/java/org/apache/cassandra/sidecar/utils/XXHash32Provider.java
new file mode 100644
index 0000000..7c45e9f
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/XXHash32Provider.java
@@ -0,0 +1,64 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+import org.apache.commons.codec.digest.XXHash32;
+
+/**
+ * Provides the XXHash32 implementation from commons-codec
+ */
+public class XXHash32Provider implements DigestAlgorithmProvider
+{
+    @Override
+    public DigestAlgorithm get(int seed)
+    {
+        return new Lz4XXHash32(seed);
+    }
+
+    /**
+     * XXHash32 implementation from LZ4
+     */
+    public static class Lz4XXHash32 implements DigestAlgorithm
+    {
+        private final XXHash32 xxHash32;
+
+        Lz4XXHash32(int seed)
+        {
+            this.xxHash32 = new XXHash32(seed);
+        }
+
+        @Override
+        public void update(byte[] buf, int off, int len)
+        {
+            xxHash32.update(buf, off, len);
+        }
+
+        @Override
+        public String digest()
+        {
+            return Long.toHexString(xxHash32.getValue());
+        }
+
+        @Override
+        public void close()
+        {
+            // XXHash32 does not require to be closed
+        }
+    }
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/restore/RestoreJobUtilTest.java b/src/test/java/org/apache/cassandra/sidecar/restore/RestoreJobUtilTest.java
index 01af3d9..8c950cc 100644
--- a/src/test/java/org/apache/cassandra/sidecar/restore/RestoreJobUtilTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/restore/RestoreJobUtilTest.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.io.TempDir;
 
 import com.datastax.driver.core.utils.UUIDs;
 import org.apache.cassandra.sidecar.exceptions.RestoreJobFatalException;
+import org.apache.cassandra.sidecar.utils.XXHash32Provider;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -43,11 +44,12 @@ class RestoreJobUtilTest
     {
         File file = new File(testDirRoot, "checksum.txt");
         Files.write(file.toPath(), "XXHash32 is employed as the hash algorithm".getBytes(StandardCharsets.UTF_8));
-        String checksum = RestoreJobUtil.checksum(file);
+        RestoreJobUtil util = new RestoreJobUtil(new XXHash32Provider());
+        String checksum = util.checksum(file);
         assertThat(checksum)
-        .describedAs("Hasher should return 32 bits checksum == 8 characters")
-        .hasSize(16)
-        .isEqualTo("ffffffffa89219eb"); // hash of "XXHash32 is employed as the hash algorithm"
+        .describedAs("Hasher should return 32 bits checksum == 4 characters")
+        .hasSize(8)
+        .isEqualTo("a0051d07"); // hash of "XXHash32 is employed as the hash algorithm"
     }
 
     @Test
diff --git a/src/test/java/org/apache/cassandra/sidecar/restore/RestoreProcessorTest.java b/src/test/java/org/apache/cassandra/sidecar/restore/RestoreProcessorTest.java
index 83ddc70..c3fafc8 100644
--- a/src/test/java/org/apache/cassandra/sidecar/restore/RestoreProcessorTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/restore/RestoreProcessorTest.java
@@ -175,7 +175,7 @@ class RestoreProcessorTest
         RestoreSlice slice = mock(RestoreSlice.class, Mockito.RETURNS_DEEP_STUBS);
         when(slice.jobId()).thenReturn(UUIDs.timeBased());
         when(slice.owner().id()).thenReturn(1);
-        when(slice.toAsyncTask(any(), any(), any(), anyDouble(), any(), any())).thenReturn(promise -> {
+        when(slice.toAsyncTask(any(), any(), any(), anyDouble(), any(), any(), any())).thenReturn(promise -> {
             Uninterruptibles.awaitUninterruptibly(latch);
             promise.complete(slice);
         });
diff --git a/src/test/java/org/apache/cassandra/sidecar/restore/RestoreSliceTaskTest.java b/src/test/java/org/apache/cassandra/sidecar/restore/RestoreSliceTaskTest.java
index 80f4f19..728f5d2 100644
--- a/src/test/java/org/apache/cassandra/sidecar/restore/RestoreSliceTaskTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/restore/RestoreSliceTaskTest.java
@@ -262,7 +262,8 @@ class RestoreSliceTaskTest
                                     SSTableImporter importer, double requiredUsableSpacePercentage,
                                     RestoreSliceDatabaseAccessor sliceDatabaseAccessor, RestoreJobStats stats)
         {
-            super(slice, s3Client, executorPool, importer, requiredUsableSpacePercentage, sliceDatabaseAccessor, stats);
+            super(slice, s3Client, executorPool, importer, requiredUsableSpacePercentage, sliceDatabaseAccessor, stats,
+                  null);
             this.slice = slice;
             this.stats = stats;
         }
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java
index 2694fb4..caf76e6 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java
@@ -97,7 +97,7 @@ class SSTableUploadHandlerTest extends BaseUploadsHandlerTest
     {
         UUID uploadId = UUID.randomUUID();
         sendUploadRequestAndVerify(context, uploadId, "ks", "tbl", "with-correct-xxhash.db",
-                                   new XXHash32Digest("7a28edc0"),
+                                   new XXHash32Digest("21228a35"),
                                    Files.size(Paths.get(FILE_TO_BE_UPLOADED)),
                                    HttpResponseStatus.OK.code(),
                                    false);
@@ -108,7 +108,7 @@ class SSTableUploadHandlerTest extends BaseUploadsHandlerTest
     {
         UUID uploadId = UUID.randomUUID();
         sendUploadRequestAndVerify(context, uploadId, "ks", "tbl", "with-correct-xxhash.db",
-                                   new XXHash32Digest("ffffffffb9510d6b", "55555555"),
+                                   new XXHash32Digest("b9510d6b", "55555555"),
                                    Files.size(Paths.get(FILE_TO_BE_UPLOADED)),
                                    HttpResponseStatus.OK.code(),
                                    false);
@@ -409,7 +409,12 @@ class SSTableUploadHandlerTest extends BaseUploadsHandlerTest
             }
 
             HttpResponse<Buffer> httpResponse = response.result();
-            assertThat(httpResponse.statusCode()).isEqualTo(expectedRetCode);
+            if (httpResponse.statusCode() != expectedRetCode)
+            {
+                context.failNow("Status code mismatched. Expected: " + expectedRetCode +
+                                "; actual: " + httpResponse.statusCode());
+                return;
+            }
 
             if (responseValidator != null)
             {
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactoryTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactoryTest.java
index 810e015..f9460ce 100644
--- a/src/test/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactoryTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/DigestVerifierFactoryTest.java
@@ -43,11 +43,10 @@ class DigestVerifierFactoryTest
         options = new HeadersMultiMap();
     }
 
-
     @Test
     void testEmptyFactoryReturnsFallbackVerifier()
     {
-        DigestVerifier verifier = new DigestVerifierFactory(vertx).verifier(options);
+        DigestVerifier verifier = newFactory().verifier(options);
         assertThat(verifier).as("should fallback to the fallback verifier when no verifiers are configured")
                             .isNotNull()
                             .isSameAs(FALLBACK_VERIFIER);
@@ -57,7 +56,7 @@ class DigestVerifierFactoryTest
     void testMd5Verifier()
     {
         options.set("content-md5", "md5-header");
-        DigestVerifier verifier = new DigestVerifierFactory(vertx).verifier(options);
+        DigestVerifier verifier = newFactory().verifier(options);
 
         assertThat(verifier).as("MD5DigestVerifier can verify MD5 content headers")
                             .isNotNull()
@@ -68,7 +67,7 @@ class DigestVerifierFactoryTest
     void testXXHashVerifier()
     {
         options.set(CONTENT_XXHASH32, "xxhash-header");
-        DigestVerifier verifier = new DigestVerifierFactory(vertx).verifier(options);
+        DigestVerifier verifier = newFactory().verifier(options);
 
         assertThat(verifier).as("XXHashDigestVerifier can verify XXHash content headers")
                             .isNotNull()
@@ -80,10 +79,15 @@ class DigestVerifierFactoryTest
     {
         options.set("content-md5", "md5-header")
                .set(CONTENT_XXHASH32, "xxhash-header");
-        DigestVerifier verifier = new DigestVerifierFactory(vertx).verifier(options);
+        DigestVerifier verifier = newFactory().verifier(options);
 
         assertThat(verifier).as("XXHashDigestVerifier is selected when both headers are present")
                             .isNotNull()
                             .isInstanceOf(XXHash32DigestVerifier.class);
     }
+
+    private DigestVerifierFactory newFactory()
+    {
+        return new DigestVerifierFactory(vertx, new XXHash32Provider(), new JdkMd5DigestProvider());
+    }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifierTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifierTest.java
index 550ef8e..0dce3e3 100644
--- a/src/test/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifierTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/MD5DigestVerifierTest.java
@@ -107,7 +107,7 @@ class MD5DigestVerifierTest
 
         public ExposeAsyncFileMD5DigestVerifier(FileSystem fs, MD5Digest md5Digest)
         {
-            super(fs, md5Digest);
+            super(fs, md5Digest, new JdkMd5DigestProvider.JdkMD5Digest());
         }
 
         @Override
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifierTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifierTest.java
index ff7566e..a5e0668 100644
--- a/src/test/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifierTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/XXHash32DigestVerifierTest.java
@@ -33,12 +33,11 @@ import io.vertx.core.file.AsyncFile;
 import io.vertx.core.file.FileSystem;
 import io.vertx.ext.web.handler.HttpException;
 import org.apache.cassandra.sidecar.common.data.XXHash32Digest;
+import org.apache.cassandra.sidecar.restore.RestoreJobUtil;
 import org.assertj.core.api.InstanceOfAssertFactories;
 
-import static org.apache.cassandra.sidecar.restore.RestoreJobUtil.checksum;
 import static org.assertj.core.api.Assertions.as;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatNullPointerException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.from;
 
@@ -52,32 +51,29 @@ class XXHash32DigestVerifierTest
     @TempDir
     static Path tempDir;
     static Path randomFilePath;
+    static RestoreJobUtil restoreJobUtil;
 
     @BeforeAll
     static void setup() throws IOException
     {
         vertx = Vertx.vertx();
 
-        randomFilePath = TestFileUtils.prepareTestFile(tempDir, "random-file.txt", 1024);
-    }
+        restoreJobUtil = new RestoreJobUtil(new XXHash32Provider());
 
-    @Test
-    void failsWhenDigestIsNull()
-    {
-        assertThatNullPointerException().isThrownBy(() -> newVerifier(null)).withMessage("digest is required");
+        randomFilePath = TestFileUtils.prepareTestFile(tempDir, "random-file.txt", 1024);
     }
 
     @Test
     void testFileDescriptorsClosedWithValidDigest() throws IOException, InterruptedException
     {
-        XXHash32Digest digest = new XXHash32Digest(checksum(randomFilePath.toFile()));
+        XXHash32Digest digest = new XXHash32Digest(restoreJobUtil.checksum(randomFilePath.toFile()));
         runTestScenario(randomFilePath, digest, false);
     }
 
     @Test
     void failsWithNonDefaultSeedAndSeedIsNotPassedAsAnOption() throws IOException, InterruptedException
     {
-        XXHash32Digest digest = new XXHash32Digest(checksum(randomFilePath.toFile(), 0x55555555));
+        XXHash32Digest digest = new XXHash32Digest(restoreJobUtil.checksum(randomFilePath.toFile(), 0x55555555));
         runTestScenario(randomFilePath, digest, true);
     }
 
@@ -85,7 +81,7 @@ class XXHash32DigestVerifierTest
     void testWithCustomSeed() throws IOException, InterruptedException
     {
         int seed = 0x55555555;
-        XXHash32Digest digest = new XXHash32Digest(checksum(randomFilePath.toFile(), seed), seed);
+        XXHash32Digest digest = new XXHash32Digest(restoreJobUtil.checksum(randomFilePath.toFile(), seed), seed);
         runTestScenario(randomFilePath, digest, false);
     }
 
@@ -142,7 +138,7 @@ class XXHash32DigestVerifierTest
 
         public ExposeAsyncFileXXHash32DigestVerifier(FileSystem fs, XXHash32Digest digest)
         {
-            super(fs, digest);
+            super(fs, digest, new XXHash32Provider.Lz4XXHash32(maybeGetSeedOrDefault(digest)));
         }
 
         @Override


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org