Skip to content

Commit

Permalink
Swap MD5 hash to CRC32 for checksum calculation (#39)
Browse files Browse the repository at this point in the history
Swap cryptographic MD5 hash for NCHF CRC32 for checksum calculation
  • Loading branch information
pirgeo authored Jan 28, 2025
1 parent d3cd059 commit 45f8872
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 33 deletions.
53 changes: 21 additions & 32 deletions lib/src/main/java/com/dynatrace/file/util/PollBasedFilePoller.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,30 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import java.util.zip.CRC32;

class PollBasedFilePoller extends FilePoller {
private static final Logger LOGGER = Logger.getLogger(PollBasedFilePoller.class.getName());
private static final String LOG_MESSAGE_FAILED_FILE_READ = "Failed to read file %s. Error: %s";

private final AtomicBoolean changedSinceLastInquiry = new AtomicBoolean(false);
private byte[] prevChecksumBytes;

private final ScheduledFuture<?> worker;
private final ScheduledExecutorService executorService;

private MessageDigest md5;
private final CRC32 crc32 = new CRC32();
private Long prevChecksumValue = null;

protected PollBasedFilePoller(Path filePath, Duration pollInterval) {
super(filePath);
if (pollInterval == null) {
throw new IllegalArgumentException("Poll interval cannot be null");
}

try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ignored) {
// ignore, since this is not something dependent on the code. MD5 should always be there.
}

executorService =
Executors.newSingleThreadScheduledExecutor(
new ThreadFactory() {
Expand All @@ -63,9 +55,6 @@ public Thread newThread(Runnable r) {
worker =
executorService.scheduleAtFixedRate(
this::poll, pollInterval.toNanos(), pollInterval.toNanos(), TimeUnit.NANOSECONDS);

// read the initial checksum
prevChecksumBytes = getChecksumBytes();
}

@Override
Expand All @@ -75,36 +64,36 @@ public boolean fileContentsUpdated() {
}

private synchronized void poll() {
byte[] latestChecksumBytes = getChecksumBytes();

if (!Arrays.equals(latestChecksumBytes, prevChecksumBytes)) {
prevChecksumBytes = latestChecksumBytes;

// The file did exist before (prevChecksumBytes != null) but doesn't anymore
// (latestChecksumBytes == null).
// This code here is only reached when prevChecksumBytes != latestChecksumBytes.
// This means the file has been deleted since the last poll, which should not trigger a
// state change.
if (latestChecksumBytes != null) {
Long latestChecksumValue = getChecksumValue();

// | latest | previous | outcome |
// | ------ | -------- | ----------------------- |
// | null | null | do nothing |
// | null | !null | do nothing |
// | !null | null | update (no file before) |
// | !null | !null | update if changed |
if (latestChecksumValue != null) {
if (!latestChecksumValue.equals(prevChecksumValue)) {
changedSinceLastInquiry.set(true);
}
prevChecksumValue = latestChecksumValue;
}
}

private synchronized byte[] getChecksumBytes() {
byte[] bytes = null;
private synchronized Long getChecksumValue() {
crc32.reset();
try {
bytes = md5.digest(Files.readAllBytes(absoluteFilePath));
byte[] fileBytes = Files.readAllBytes(absoluteFilePath);
crc32.update(fileBytes, 0, fileBytes.length);
} catch (IOException e) {
LOGGER.warning(() -> String.format(LOG_MESSAGE_FAILED_FILE_READ, absoluteFilePath, e));
} finally {
md5.reset();
return null;
}
return bytes;
return crc32.getValue();
}

@Override
public void close() throws IOException {
public void close() {
worker.cancel(true);
executorService.shutdown();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ static void filePollerUpdatesOnlyOnCreateAndChangeNotOnDelete(FilePoller poller,

// create the file again
Files.createFile(path);
await().atMost(1, TimeUnit.SECONDS).until(poller::fileContentsUpdated);
// creating an empty file does not trigger an update (no new information to be read)
await().atMost(1, TimeUnit.SECONDS).until(() -> !poller.fileContentsUpdated());

// adding new content to the new file will trigger an update.
Files.write(path, "Some content".getBytes());
await().atMost(1, TimeUnit.SECONDS).until(poller::fileContentsUpdated);
}
Expand Down

0 comments on commit 45f8872

Please sign in to comment.