Skip to content

Commit 91cf0a8

Browse files
committed
[feature] Add checksums for binary files to the Journal
1 parent e59f99a commit 91cf0a8

File tree

7 files changed

+566
-140
lines changed

7 files changed

+566
-140
lines changed

src/org/exist/storage/AbstractBinaryLoggable.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
import org.apache.logging.log4j.LogManager;
2424
import org.apache.logging.log4j.Logger;
2525
import org.exist.storage.journal.AbstractLoggable;
26+
import org.exist.util.crypto.digest.DigestType;
27+
import org.exist.util.crypto.digest.MessageDigest;
2628

2729
import javax.annotation.Nullable;
30+
import java.nio.ByteBuffer;
2831
import java.nio.file.Path;
2932
import java.nio.file.Paths;
3033

@@ -36,6 +39,8 @@
3639
public abstract class AbstractBinaryLoggable extends AbstractLoggable {
3740
private static final Logger LOG = LogManager.getLogger(AbstractBinaryLoggable.class);
3841

42+
protected final static byte NO_DIGEST_TYPE = 0x0;
43+
3944
public AbstractBinaryLoggable(final byte type, final long transactionId) {
4045
super(type, transactionId);
4146
}
@@ -111,4 +116,38 @@ protected static void checkPathLen(final String loggableName, final String pathN
111116
LOG.error(loggableName + ": " + pathName + " path needs more than 65,535 bytes. Path will be truncated: " + new String(path, UTF_8));
112117
}
113118
}
119+
120+
/**
121+
* Writes a message digest to a buffer.
122+
*
123+
* @param out the buffer to write the message digest to.
124+
* @param messageDigest the message digest to write to the buffer.
125+
*/
126+
protected static void writeMessageDigest(final ByteBuffer out, @Nullable final MessageDigest messageDigest) {
127+
if (messageDigest == null) {
128+
out.put(NO_DIGEST_TYPE);
129+
} else {
130+
out.put(messageDigest.getDigestType().getId());
131+
out.put(messageDigest.getValue(), 0, messageDigest.getDigestType().getDigestLengthBytes());
132+
}
133+
}
134+
135+
/**
136+
* Reads a message digest from a buffer.
137+
*
138+
* @param in the buffer to read the message digest from.
139+
*
140+
* @return the message digest read from the buffer.
141+
*/
142+
protected static @Nullable MessageDigest readMessageDigest(final ByteBuffer in) {
143+
final byte digestTypeId = in.get();
144+
if (digestTypeId == NO_DIGEST_TYPE) {
145+
return null;
146+
} else {
147+
final DigestType digestType = DigestType.forId(digestTypeId);
148+
final byte[] digestValue = new byte[digestType.getDigestLengthBytes()];
149+
in.get(digestValue);
150+
return new MessageDigest(digestType, digestValue);
151+
}
152+
}
114153
}

src/org/exist/storage/CreateBinaryLoggable.java

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,38 +25,49 @@
2525
import java.nio.file.Files;
2626
import java.nio.file.Path;
2727
import java.nio.file.StandardCopyOption;
28+
import java.util.Arrays;
2829

2930
import org.exist.storage.journal.LogException;
3031
import org.exist.storage.txn.Txn;
32+
import org.exist.util.FileUtils;
33+
import org.exist.util.crypto.digest.MessageDigest;
34+
import org.exist.util.crypto.digest.StreamableDigest;
3135

3236
/**
3337
* @author Adam Retter <[email protected]>
3438
*
3539
* Serialized binary format is as follows:
3640
*
37-
* [walDataPathLen, walDataPath, createPathLen, createPath]
41+
* [walDataPathLen, walDataPath, walDataDigestType, walDataDigest, createPathLen, createPath]
3842
*
39-
* walDataPathLen: 2 bytes, unsigned short
40-
* walDataPath: var length bytes, UTF-8 encoded java.lang.String
41-
* createPathLen: 2 bytes, unsigned short
42-
* createPath: var length bytes, UTF-8 encoded java.lang.String
43+
* walDataPathLen: 2 bytes, unsigned short
44+
* walDataPath: var length bytes, UTF-8 encoded java.lang.String
45+
* walDataDigestType: 1 byte
46+
* walDataDigest: n-bytes, where n is deteremined by {@code walDataDigestType}
47+
* createPathLen: 2 bytes, unsigned short
48+
* createPath: var length bytes, UTF-8 encoded java.lang.String
4349
*/
4450
public class CreateBinaryLoggable extends AbstractBinaryLoggable {
45-
private byte[] walDataPath; // the data to use for the file to be created
46-
private byte[] createPath; // the file to be created
51+
private byte[] walDataPath; // the data to use for the file to be created
52+
private MessageDigest walDataDigest;
53+
private byte[] createPath; // the file to be created
4754

4855
/**
4956
* Creates a new instance of CreateBinaryLoggable.
5057
*
5158
* @param broker The database broker.
5259
* @param txn The database transaction.
5360
* @param walData A copy of the data that was stored for {@code create} file before it was actually created (i.e. the new value).
61+
* @param walDataDigest digest of the {@code walData} content
5462
* @param create The file that is to be created in the database.
5563
*/
56-
public CreateBinaryLoggable(final DBBroker broker, final Txn txn, final Path walData, final Path create) {
64+
public CreateBinaryLoggable(final DBBroker broker, final Txn txn, final Path walData,
65+
final MessageDigest walDataDigest, final Path create) {
5766
super(NativeBroker.LOG_CREATE_BINARY, txn.getId());
5867
this.walDataPath = getPathData(walData);
5968
checkPathLen(getClass().getSimpleName(), "walDataPath", walDataPath);
69+
this.walDataDigest = walDataDigest;
70+
6071
this.createPath = getPathData(create);
6172
checkPathLen(getClass().getSimpleName(), "createPath", createPath);
6273
}
@@ -76,6 +87,8 @@ public int getLogSize() {
7687
return
7788
2 +
7889
walDataPath.length +
90+
1 +
91+
walDataDigest.getDigestType().getDigestLengthBytes() +
7992
2 +
8093
createPath.length;
8194
}
@@ -84,6 +97,8 @@ public int getLogSize() {
8497
public void write(final ByteBuffer out) {
8598
out.putShort(asUnsignedShort(walDataPath.length));
8699
out.put(walDataPath);
100+
writeMessageDigest(out, walDataDigest);
101+
87102
out.putShort(asUnsignedShort(createPath.length));
88103
out.put(createPath);
89104
}
@@ -93,6 +108,7 @@ public void read(final ByteBuffer in) {
93108
final int walDataPathLen = asSignedInt(in.getShort());
94109
this.walDataPath = new byte[walDataPathLen];
95110
in.get(walDataPath);
111+
this.walDataDigest = readMessageDigest(in);
96112

97113
final int createPathLen = asSignedInt(in.getShort());
98114
this.createPath = new byte[createPathLen];
@@ -113,7 +129,28 @@ public void redo() throws LogException {
113129
}
114130

115131
try {
132+
// ensure the integrity of the walData file
133+
final StreamableDigest walDataStreamableDigest = walDataDigest.getDigestType().newStreamableDigest();
134+
FileUtils.digest(walData, walDataStreamableDigest);
135+
if (!Arrays.equals(walDataStreamableDigest.getMessageDigest(), walDataDigest.getValue())) {
136+
throw new LogException("Cannot redo creation of binary resource: "
137+
+ create.toAbsolutePath().toString() + " from "
138+
+ walData.toAbsolutePath().toString() + ", digest of walData file is invalid");
139+
}
140+
141+
// perform the redo - copy
116142
Files.copy(walData, create, StandardCopyOption.REPLACE_EXISTING);
143+
144+
// ensure the integrity of the copy
145+
walDataStreamableDigest.reset();
146+
FileUtils.digest(create, walDataStreamableDigest);
147+
if (!Arrays.equals(walDataStreamableDigest.getMessageDigest(), walDataDigest.getValue())) {
148+
FileUtils.deleteQuietly(create);
149+
throw new LogException("Cannot redo creation of binary resource: "
150+
+ create.toAbsolutePath().toString() + " from "
151+
+ walData.toAbsolutePath().toString() + ", checksum of new create file is invalid");
152+
}
153+
117154
} catch(final IOException ioe) {
118155
throw new LogException("Cannot redo creation of binary resource: "
119156
+ create.toAbsolutePath().toString(), ioe);
@@ -127,20 +164,36 @@ public void undo() throws LogException {
127164
final Path walData = getPath(walDataPath);
128165
final Path create = getPath(createPath);
129166

130-
// ensure integrity of write-ahead-data file first!
131-
if (!Files.exists(walData)) {
132-
throw new LogException("Cannot redo creation of binary resource: "
133-
+ create.toAbsolutePath().toString() + ", missing write ahead data: "
134-
+ walData.toAbsolutePath().toString());
135-
}
136-
137-
138-
// cover the use-case where the previous operation was a delete
139-
if (!Files.exists(create)) {
140-
return;
141-
}
142-
143167
try {
168+
// ensure integrity of the walData file first!
169+
if (!Files.exists(walData)) {
170+
throw new LogException("Cannot redo creation of binary resource: "
171+
+ create.toAbsolutePath().toString() + ", missing write ahead data: "
172+
+ walData.toAbsolutePath().toString());
173+
}
174+
175+
// ensure integrity of walData file by checksum
176+
final StreamableDigest walDataStreamableDigest = walDataDigest.getDigestType().newStreamableDigest();
177+
FileUtils.digest(walData, walDataStreamableDigest);
178+
if (!Arrays.equals(walDataStreamableDigest.getMessageDigest(), walDataDigest.getValue())) {
179+
throw new LogException("Cannot undo creation of binary resource: "
180+
+ create.toAbsolutePath().toString() + ", checksum of walData file is invalid");
181+
}
182+
183+
// cover the use-case where the previous operation was a delete
184+
if (!Files.exists(create)) {
185+
return;
186+
}
187+
188+
// ensure that no one has interfered with the createdFile
189+
walDataStreamableDigest.reset();
190+
FileUtils.digest(create, walDataStreamableDigest);
191+
if (!Arrays.equals(walDataStreamableDigest.getMessageDigest(), walDataDigest.getValue())) {
192+
throw new LogException("Cannot undo creation of binary resource: "
193+
+ create.toAbsolutePath().toString() + ", checksum is invalid");
194+
}
195+
196+
// preform the undo - delete
144197
Files.delete(create);
145198
} catch(final IOException ioe) {
146199
throw new LogException("Cannot undo creation of binary resource: "

src/org/exist/storage/DeleteBinaryLoggable.java

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,42 +23,57 @@
2323
import org.exist.storage.journal.LogException;
2424
import org.exist.storage.txn.Txn;
2525
import org.exist.util.FileUtils;
26+
import org.exist.util.crypto.digest.MessageDigest;
27+
import org.exist.util.crypto.digest.StreamableDigest;
2628

2729
import java.io.IOException;
2830
import java.nio.ByteBuffer;
2931
import java.nio.file.Files;
3032
import java.nio.file.Path;
33+
import java.util.Arrays;
3134

3235
/**
3336
* @author Adam Retter <[email protected]>
3437
*
3538
* Serialized binary format is as follows:
3639
*
37-
* [deletePathLen, deletePath, dataPathLen, dataPath]
40+
* [deletePathLen, deletePath, deleteDigestType, deleteDigest, dataPathLen, dataPath, dataDigestType, dataDigest]
3841
*
39-
* deletePathLen: 2 bytes, unsigned short
40-
* deletePath: var length bytes, UTF-8 encoded java.lang.String
41-
* dataPathLen: 2 bytes, unsigned short
42-
* dataPath: var length bytes, UTF-8 encoded java.lang.String
42+
* deletePathLen: 2 bytes, unsigned short
43+
* deletePath: var length bytes, UTF-8 encoded java.lang.String
44+
* deleteDigestType: 1 byte
45+
* deleteDigest: n-bytes, where n is deteremined by {@code deleteDigestType}
46+
* dataPathLen: 2 bytes, unsigned short
47+
* dataPath: var length bytes, UTF-8 encoded java.lang.String
48+
* dataDigestType: 1 byte
49+
* dataDigest: n-bytes, where n is deteremined by {@code dataDigestType}
4350
*/
4451
public class DeleteBinaryLoggable extends AbstractBinaryLoggable {
45-
private byte[] deletePath; // the file to be deleted
46-
private byte[] dataPath; // the data before the file was deleted (i.e. the current value)
52+
private byte[] deletePath; // the file to be deleted
53+
private MessageDigest deleteDigest;
54+
private byte[] dataPath; // the data before the file was deleted (i.e. the current value)
55+
private MessageDigest dataDigest;
4756

4857
/**
4958
* Creates a new instance of DeleteBinaryLoggable.
5059
*
5160
* @param broker The database broker.
5261
* @param txn The database transaction.
5362
* @param delete The file that is to be deleted from the database.
63+
* @param deleteDigest digest of the {@code delete} content
5464
* @param data A copy of the data before the file was deleted (i.e. the current value).
65+
* @param dataDigest digest of the {@code data} content
5566
*/
56-
public DeleteBinaryLoggable(final DBBroker broker, final Txn txn, final Path delete, final Path data) {
67+
public DeleteBinaryLoggable(final DBBroker broker, final Txn txn, final Path delete,
68+
final MessageDigest deleteDigest, final Path data, final MessageDigest dataDigest) {
5769
super(NativeBroker.LOG_DELETE_BINARY, txn.getId());
5870
this.deletePath = getPathData(delete);
5971
checkPathLen(getClass().getSimpleName(), "deletePath", deletePath);
72+
this.deleteDigest = deleteDigest;
73+
6074
this.dataPath = getPathData(data);
6175
checkPathLen(getClass().getSimpleName(), "dataPath", dataPath);
76+
this.dataDigest = dataDigest;
6277
}
6378

6479
/**
@@ -76,27 +91,36 @@ public int getLogSize() {
7691
return
7792
2 +
7893
deletePath.length +
94+
1 +
95+
deleteDigest.getDigestType().getDigestLengthBytes() +
7996
2 +
80-
dataPath.length;
97+
dataPath.length +
98+
1 +
99+
dataDigest.getDigestType().getDigestLengthBytes();
81100
}
82101

83102
@Override
84103
public void write(final ByteBuffer out) {
85104
out.putShort(asUnsignedShort(deletePath.length));
86105
out.put(deletePath);
106+
writeMessageDigest(out, deleteDigest);
107+
87108
out.putShort(asUnsignedShort(dataPath.length));
88109
out.put(dataPath);
110+
writeMessageDigest(out, dataDigest);
89111
}
90112

91113
@Override
92114
public void read(final ByteBuffer in) {
93115
final int replacePathLen = asSignedInt(in.getShort());
94116
this.deletePath = new byte[replacePathLen];
95117
in.get(deletePath);
118+
this.deleteDigest = readMessageDigest(in);
96119

97120
final int dataPathLen = asSignedInt(in.getShort());
98121
this.dataPath = new byte[dataPathLen];
99122
in.get(dataPath);
123+
this.dataDigest = readMessageDigest(in);
100124
}
101125

102126
@Override
@@ -111,6 +135,17 @@ public void redo() throws LogException {
111135
}
112136

113137
try {
138+
final Path data = getPath(dataPath);
139+
140+
// ensure integrity of data file first!
141+
final StreamableDigest dataStreamableDigest = dataDigest.getDigestType().newStreamableDigest();
142+
FileUtils.digest(data, dataStreamableDigest);
143+
if (!Arrays.equals(dataStreamableDigest.getMessageDigest(), dataDigest.getValue())) {
144+
throw new LogException("Cannot redo delete of binary resource: "
145+
+ delete.toAbsolutePath().toString() + ", checksum of data file is invalid");
146+
}
147+
148+
// preform the redo - delete
114149
FileUtils.delete(delete);
115150
} catch(final IOException ioe) {
116151
throw new LogException("Cannot redo delete of binary resource: "
@@ -130,14 +165,32 @@ public void undo() throws LogException {
130165
+ delete.toAbsolutePath().toString() + ", already exists");
131166
}
132167

133-
// ensure integrity of data file first!
134-
if (!Files.exists(data)) {
135-
throw new LogException("Cannot undo delete of binary resource: "
136-
+ data.toAbsolutePath().toString() + ", missing data file");
137-
}
138-
139168
try {
169+
// ensure integrity of the data file first!
170+
if (!Files.exists(data)) {
171+
throw new LogException("Cannot undo delete of binary resource: "
172+
+ data.toAbsolutePath().toString() + ", missing data file");
173+
}
174+
175+
// ensure integrity of data file by checksum
176+
final StreamableDigest dataStreamableDigest = dataDigest.getDigestType().newStreamableDigest();
177+
FileUtils.digest(data, dataStreamableDigest);
178+
if (!Arrays.equals(dataStreamableDigest.getMessageDigest(), dataDigest.getValue())) {
179+
throw new LogException("Cannot undo delete of binary resource: "
180+
+ data.toAbsolutePath().toString() + ", data file checksum is invalid");
181+
}
182+
183+
// perform the undo - copy
140184
FileUtils.copy(data, delete);
185+
186+
// ensure the integrity of the copy
187+
dataStreamableDigest.reset();
188+
FileUtils.digest(delete, dataStreamableDigest);
189+
if (!Arrays.equals(dataStreamableDigest.getMessageDigest(), dataDigest.getValue())) {
190+
throw new LogException("Cannot undo delete of binary resource: "
191+
+ delete.toAbsolutePath().toString() + " from "
192+
+ data.toAbsolutePath().toString() + ", checksum of new delete file is invalid");
193+
}
141194
} catch(final IOException ioe) {
142195
throw new LogException("Cannot undo delete of binary resource: "
143196
+ delete.toAbsolutePath().toString(), ioe);

0 commit comments

Comments
 (0)