ChecksumUtility.java

1
package com.renomad.minum.database;
2
3
import com.renomad.minum.utils.CryptoUtils;
4
5
import java.io.IOException;
6
import java.nio.charset.StandardCharsets;
7
import java.nio.file.Files;
8
import java.nio.file.Path;
9
import java.security.MessageDigest;
10
import java.security.NoSuchAlgorithmException;
11
import java.util.Collection;
12
import java.util.List;
13
14
/**
15
 * This class contains some functions related to the "checksum" feature
16
 * of the database.  The "checksum" is a hash that is created when the
17
 * database writes data to a file, and is checked when reading that file.
18
 * <br>
19
 * If the value is different than expected while reading, an exception will be thrown
20
 * indicating data corruption.
21
 */
22
public class ChecksumUtility {
23
24
    private ChecksumUtility() {
25
        // not intended to be instantiated
26
    }
27
28
    /**
29
     * Given a path to a consolidated database file, check its data against
30
     * the recorded checksum and throw an exception if it doesn't match.
31
     * @param fullPathToConsolidatedFile the path to a consolidated database file
32
     * @param data the list of strings of data in a consolidated database file
33
     */
34
    static boolean compareWithChecksum(Path fullPathToConsolidatedFile, List<String> data) {
35
        // check against the checksum, if it exists. If it is not there or blank, just move on.
36
        Path checksumPath = fullPathToConsolidatedFile.resolveSibling(fullPathToConsolidatedFile.getFileName() + ".checksum");
37 1 1. compareWithChecksum : negated conditional → KILLED
        if (Files.exists(checksumPath)) {
38
            String existingChecksumValue;
39
            try {
40
                existingChecksumValue = Files.readString(checksumPath);
41
            } catch (IOException e) {
42
                throw new DbChecksumException(e);
43
            }
44
            String checksumString = buildChecksum(data);
45 1 1. compareWithChecksum : negated conditional → KILLED
            if (!checksumString.equals(existingChecksumValue)) {
46
                throw new DbChecksumException(generateChecksumErrorMessage(fullPathToConsolidatedFile));
47
            }
48
        }
49 1 1. compareWithChecksum : replaced boolean return with false for com/renomad/minum/database/ChecksumUtility::compareWithChecksum → KILLED
        return true;
50
    }
51
52
    static String generateChecksumErrorMessage(Path fullPathToConsolidatedFile) {
53 1 1. generateChecksumErrorMessage : replaced return value with "" for com/renomad/minum/database/ChecksumUtility::generateChecksumErrorMessage → KILLED
        return """
54
                
55
                WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
56
                **************************************************************************************
57
                **************************************************************************************
58
                
59
                Checksum failure for %s
60
                
61
                THIS FILE IS CORRUPTED, AND NEEDS TO BE RESTORED FROM BACKUP!
62
                
63
                The checksum file is at %s.checksum
64
                
65
                Next steps: This warning means the data does not align with its checksum, meaning
66
                it has changed by something other than the program.  That is, it is corrupted data.
67
                The best thing is to restore the data from backup for this.  Other options are to
68
                review the data.  Deleting the checksum file will cause this complaint to stop.
69
                
70
                **************************************************************************************
71
                **************************************************************************************
72
                WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
73
                
74
                """.stripIndent().formatted(fullPathToConsolidatedFile, fullPathToConsolidatedFile);
75
    }
76
77
    static String buildChecksum(Collection<String> updatedData) {
78
        // build a hash for this data
79
        MessageDigest messageDigestSha256 = getMessageDigest("SHA-256");
80
        for (String item : updatedData) {
81 1 1. buildChecksum : removed call to java/security/MessageDigest::update → KILLED
            messageDigestSha256.update(item.getBytes(StandardCharsets.US_ASCII));
82
        }
83
        byte[] hash = messageDigestSha256.digest();
84 1 1. buildChecksum : replaced return value with "" for com/renomad/minum/database/ChecksumUtility::buildChecksum → KILLED
        return CryptoUtils.bytesToHex(hash);
85
    }
86
87
    static MessageDigest getMessageDigest(String algorithm) {
88
        try {
89 1 1. getMessageDigest : replaced return value with null for com/renomad/minum/database/ChecksumUtility::getMessageDigest → KILLED
            return MessageDigest.getInstance(algorithm);
90
        } catch (NoSuchAlgorithmException e) {
91
            throw new DbChecksumException(e);
92
        }
93
    }
94
95
}

Mutations

37

1.1
Location : compareWithChecksum
Killed by : com.renomad.minum.database.ChecksumUtilityTests.testCompareWithChecksum_NegativeCase_ChecksumDoesNotExist(com.renomad.minum.database.ChecksumUtilityTests)
negated conditional → KILLED

45

1.1
Location : compareWithChecksum
Killed by : com.renomad.minum.database.ChecksumUtilityTests.testCompareWithChecksum_NegativeCase_ChecksumConflict(com.renomad.minum.database.ChecksumUtilityTests)
negated conditional → KILLED

49

1.1
Location : compareWithChecksum
Killed by : com.renomad.minum.database.ChecksumUtilityTests.testCompareWithChecksum_NegativeCase_ChecksumDoesNotExist(com.renomad.minum.database.ChecksumUtilityTests)
replaced boolean return with false for com/renomad/minum/database/ChecksumUtility::compareWithChecksum → KILLED

53

1.1
Location : generateChecksumErrorMessage
Killed by : com.renomad.minum.database.ChecksumUtilityTests.testCompareWithChecksum_NegativeCase_ChecksumConflict(com.renomad.minum.database.ChecksumUtilityTests)
replaced return value with "" for com/renomad/minum/database/ChecksumUtility::generateChecksumErrorMessage → KILLED

81

1.1
Location : buildChecksum
Killed by : com.renomad.minum.database.DbEngine2Tests.test_LoadingData_MultipleThreads(com.renomad.minum.database.DbEngine2Tests)
removed call to java/security/MessageDigest::update → KILLED

84

1.1
Location : buildChecksum
Killed by : com.renomad.minum.database.DbEngine2Tests.test_LoadingData_MultipleThreads(com.renomad.minum.database.DbEngine2Tests)
replaced return value with "" for com/renomad/minum/database/ChecksumUtility::buildChecksum → KILLED

89

1.1
Location : getMessageDigest
Killed by : com.renomad.minum.database.ChecksumUtilityTests.testCompareWithChecksum_NegativeCase_ChecksumConflict(com.renomad.minum.database.ChecksumUtilityTests)
replaced return value with null for com/renomad/minum/database/ChecksumUtility::getMessageDigest → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0