Skip to content

Commit 1fd1349

Browse files
authored
RATIS-2361. Change MD5Hash to value-based. (#1316)
1 parent e74f548 commit 1fd1349

File tree

6 files changed

+150
-178
lines changed

6 files changed

+150
-178
lines changed

ratis-common/dev-support/findbugsExcludeFile.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@
1919
<Class name="org.apache.ratis.datastream.impl.DataStreamReplyByteBuffer$Builder" />
2020
<Bug pattern="EI_EXPOSE_REP2" />
2121
</Match>
22-
<Match>
23-
<Class name="org.apache.ratis.io.MD5Hash" />
24-
<Bug pattern="CT_CONSTRUCTOR_THROW" />
25-
</Match>
2622
<Match>
2723
<Class name="org.apache.ratis.protocol.GroupInfoReply" />
2824
<Bug pattern="EI_EXPOSE_REP2" />
Lines changed: 59 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/*
22
* Licensed to the Apache Software Foundation (ASF) under one
33
* or more contributor license agreements. See the NOTICE file
44
* distributed with this work for additional information
@@ -18,157 +18,73 @@
1818

1919
package org.apache.ratis.io;
2020

21-
import java.io.DataInput;
22-
import java.io.DataOutput;
23-
import java.io.IOException;
24-
import java.io.InputStream;
25-
import java.security.MessageDigest;
26-
import java.security.NoSuchAlgorithmException;
27-
import java.util.Arrays;
28-
29-
public class MD5Hash {
30-
public static final int MD5_LEN = 16;
31-
32-
private static final ThreadLocal<MessageDigest> DIGESTER_FACTORY =
33-
ThreadLocal.withInitial(MD5Hash::newDigester);
34-
35-
public static MessageDigest newDigester() {
36-
try {
37-
return MessageDigest.getInstance("MD5");
38-
} catch (NoSuchAlgorithmException e) {
39-
throw new IllegalStateException("Failed to create MessageDigest for MD5", e);
40-
}
41-
}
42-
43-
private byte[] digest;
44-
45-
/** Constructs an MD5Hash. */
46-
public MD5Hash() {
47-
this.digest = new byte[MD5_LEN];
48-
}
49-
50-
/** Constructs an MD5Hash from a hex string. */
51-
public MD5Hash(String hex) {
52-
setDigest(hex);
53-
}
54-
55-
/** Constructs an MD5Hash with a specified value. */
56-
public MD5Hash(byte[] digest) {
57-
if (digest.length != MD5_LEN) {
58-
throw new IllegalArgumentException("Wrong length: " + digest.length);
59-
}
60-
this.digest = digest.clone();
61-
}
62-
63-
public void readFields(DataInput in) throws IOException {
64-
in.readFully(digest);
65-
}
66-
67-
/** Constructs, reads and returns an instance. */
68-
public static MD5Hash read(DataInput in) throws IOException {
69-
MD5Hash result = new MD5Hash();
70-
result.readFields(in);
71-
return result;
72-
}
73-
74-
public void write(DataOutput out) throws IOException {
75-
out.write(digest);
76-
}
77-
78-
/** Copy the contents of another instance into this instance. */
79-
public void set(MD5Hash that) {
80-
System.arraycopy(that.digest, 0, this.digest, 0, MD5_LEN);
81-
}
82-
83-
/** Returns the digest bytes. */
84-
public byte[] getDigest() {
85-
return digest.clone();
86-
}
87-
88-
/** Construct a hash value for a byte array. */
89-
public static MD5Hash digest(byte[] data) {
90-
return digest(data, 0, data.length);
91-
}
92-
93-
/**
94-
* Create a thread local MD5 digester
95-
*/
96-
public static MessageDigest getDigester() {
97-
MessageDigest digester = DIGESTER_FACTORY.get();
98-
digester.reset();
99-
return digester;
100-
}
21+
import org.apache.ratis.util.MemoizedSupplier;
22+
import org.apache.ratis.util.Preconditions;
10123

102-
/** Construct a hash value for the content from the InputStream. */
103-
public static MD5Hash digest(InputStream in) throws IOException {
104-
final byte[] buffer = new byte[4*1024];
24+
import java.nio.ByteBuffer;
25+
import java.util.Arrays;
26+
import java.util.Objects;
27+
import java.util.function.Supplier;
10528

106-
final MessageDigest digester = getDigester();
107-
for(int n; (n = in.read(buffer)) != -1; ) {
108-
digester.update(buffer, 0, n);
29+
/**
30+
* A MD5 hash value.
31+
* <p>
32+
* This is a value-based class.
33+
*/
34+
public final class MD5Hash {
35+
public static final int MD5_LENGTH = 16;
36+
37+
/** @return an instance with the given digest in a (case-insensitive) hexadecimals. */
38+
public static MD5Hash newInstance(String digestHexadecimals) {
39+
Objects.requireNonNull(digestHexadecimals, "digestHexadecimals == null");
40+
Preconditions.assertSame(2 * MD5_LENGTH, digestHexadecimals.length(), "digestHexadecimals");
41+
42+
final byte[] digest = new byte[MD5_LENGTH];
43+
for (int i = 0; i < MD5_LENGTH; i++) {
44+
final int j = i << 1;
45+
digest[i] = (byte) (charToNibble(digestHexadecimals, j) << 4 |
46+
charToNibble(digestHexadecimals, j + 1));
10947
}
110-
111-
return new MD5Hash(digester.digest());
112-
}
113-
114-
/** Construct a hash value for a byte array. */
115-
public static MD5Hash digest(byte[] data, int start, int len) {
116-
byte[] digest;
117-
MessageDigest digester = getDigester();
118-
digester.update(data, start, len);
119-
digest = digester.digest();
12048
return new MD5Hash(digest);
12149
}
12250

123-
/** Construct a hash value for an array of byte array. */
124-
public static MD5Hash digest(byte[][] dataArr, int start, int len) {
125-
byte[] digest;
126-
MessageDigest digester = getDigester();
127-
for (byte[] data : dataArr) {
128-
digester.update(data, start, len);
129-
}
130-
digest = digester.digest();
131-
return new MD5Hash(digest);
51+
/** @return an instance with the given digest. */
52+
public static MD5Hash newInstance(byte[] digest) {
53+
Objects.requireNonNull(digest, "digest == null");
54+
Preconditions.assertSame(MD5_LENGTH, digest.length, "digest");
55+
return new MD5Hash(digest.clone());
13256
}
13357

134-
/** Construct a half-sized version of this MD5. Fits in a long **/
135-
public long halfDigest() {
136-
long value = 0;
137-
for (int i = 0; i < 8; i++) {
138-
value |= ((digest[i] & 0xffL) << (8*(7-i)));
139-
}
140-
return value;
58+
private final byte[] digest;
59+
private final Supplier<String> digestString;
60+
61+
private MD5Hash(byte[] digest) {
62+
this.digest = digest;
63+
this.digestString = MemoizedSupplier.valueOf(() -> digestToString(digest));
14164
}
14265

143-
/**
144-
* Return a 32-bit digest of the MD5.
145-
* @return the first 4 bytes of the md5
146-
*/
147-
public int quarterDigest() {
148-
int value = 0;
149-
for (int i = 0; i < 4; i++) {
150-
value |= ((digest[i] & 0xff) << (8*(3-i)));
151-
}
152-
return value;
66+
/** @return the digest wrapped by a read-only {@link ByteBuffer}. */
67+
public ByteBuffer getDigest() {
68+
return ByteBuffer.wrap(digest).asReadOnlyBuffer();
15369
}
15470

155-
/** Returns true iff <code>o</code> is an MD5Hash whose digest contains the
156-
* same values. */
15771
@Override
158-
public boolean equals(Object o) {
159-
if (!(o instanceof MD5Hash)) {
72+
public boolean equals(Object object) {
73+
if (this == object) {
74+
return true;
75+
} else if (!(object instanceof MD5Hash)) {
16076
return false;
16177
}
162-
MD5Hash other = (MD5Hash)o;
163-
return Arrays.equals(this.digest, other.digest);
78+
final MD5Hash that = (MD5Hash) object;
79+
return Arrays.equals(this.digest, that.digest);
16480
}
16581

166-
/** Returns a hash code value for this object.
167-
* Only uses the first 4 bytes, since md5s are evenly distributed.
168-
*/
16982
@Override
17083
public int hashCode() {
171-
return quarterDigest();
84+
return ((digest[0] & 0xFF) << 24)
85+
| ((digest[1] & 0xFF) << 16)
86+
| ((digest[2] & 0xFF) << 8)
87+
| (digest[3] & 0xFF);
17288
}
17389

17490
private static final char[] HEX_DIGITS =
@@ -177,37 +93,30 @@ public int hashCode() {
17793
/** Returns a string representation of this object. */
17894
@Override
17995
public String toString() {
180-
StringBuilder buf = new StringBuilder(MD5_LEN*2);
181-
for (int i = 0; i < MD5_LEN; i++) {
96+
return digestString.get();
97+
}
98+
99+
static String digestToString(byte[] digest) {
100+
StringBuilder buf = new StringBuilder(MD5_LENGTH *2);
101+
for (int i = 0; i < MD5_LENGTH; i++) {
182102
int b = digest[i];
183103
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
184104
buf.append(HEX_DIGITS[b & 0xf]);
185105
}
186106
return buf.toString();
187107
}
188108

189-
/** Sets the digest value from a hex string. */
190-
public void setDigest(String hex) {
191-
if (hex.length() != MD5_LEN*2) {
192-
throw new IllegalArgumentException("Wrong length: " + hex.length());
193-
}
194-
this.digest = new byte[MD5_LEN];
195-
for (int i = 0; i < MD5_LEN; i++) {
196-
int j = i << 1;
197-
this.digest[i] = (byte)(charToNibble(hex.charAt(j)) << 4 |
198-
charToNibble(hex.charAt(j+1)));
199-
}
200-
}
201-
202-
private static int charToNibble(char c) {
109+
private static int charToNibble(String hexadecimals, int i) {
110+
final char c = hexadecimals.charAt(i);
203111
if (c >= '0' && c <= '9') {
204112
return c - '0';
205113
} else if (c >= 'a' && c <= 'f') {
206114
return 0xa + (c - 'a');
207115
} else if (c >= 'A' && c <= 'F') {
208116
return 0xA + (c - 'A');
209117
} else {
210-
throw new RuntimeException("Not a hex character: " + c);
118+
throw new IllegalArgumentException(
119+
"Found a non-hexadecimal character '" + c + "' at index " + i + " in \"" + hexadecimals + "\"");
211120
}
212121
}
213122
}

ratis-common/src/main/java/org/apache/ratis/util/MD5FileUtil.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
package org.apache.ratis.util;
1919

2020
import org.apache.ratis.io.MD5Hash;
21-
import org.slf4j.Logger;
22-
import org.slf4j.LoggerFactory;
2321

2422
import java.io.BufferedReader;
2523
import java.io.File;
@@ -29,16 +27,21 @@
2927
import java.nio.charset.StandardCharsets;
3028
import java.nio.file.StandardOpenOption;
3129
import java.security.MessageDigest;
30+
import java.security.NoSuchAlgorithmException;
3231
import java.util.Optional;
3332
import java.util.regex.Matcher;
3433
import java.util.regex.Pattern;
3534

36-
public abstract class MD5FileUtil {
37-
public static final Logger LOG = LoggerFactory.getLogger(MD5FileUtil.class);
35+
public final class MD5FileUtil {
36+
private MD5FileUtil() {}
3837

39-
// TODO: we should provide something like Hadoop's checksum fs for the local filesystem
40-
// so that individual state machines do not have to deal with checksumming/corruption prevention.
41-
// Keep the checksum and data in the same block format instead of individual files.
38+
public static MessageDigest newMD5() {
39+
try {
40+
return MessageDigest.getInstance("MD5");
41+
} catch (NoSuchAlgorithmException e) {
42+
throw new IllegalStateException("Failed to create MessageDigest for MD5", e);
43+
}
44+
}
4245

4346
public static final String MD5_SUFFIX = ".md5";
4447
private static final String LINE_REGEX = "([0-9a-f]{32}) [ *](.+)";
@@ -105,15 +108,15 @@ public static MD5Hash readStoredMd5ForFile(File dataFile) throws IOException {
105108
referencedFile.getName() + " but we expected it to reference " +
106109
dataFile);
107110
}
108-
return new MD5Hash(storedHash);
111+
return MD5Hash.newInstance(storedHash);
109112
}
110113

111114
/**
112115
* Read dataFile and compute its MD5 checksum.
113116
*/
114117
public static MD5Hash computeMd5ForFile(File dataFile) throws IOException {
115118
final int bufferSize = SizeInBytes.ONE_MB.getSizeInt();
116-
final MessageDigest digester = MD5Hash.getDigester();
119+
final MessageDigest digester = newMD5();
117120
try (FileChannel in = FileUtils.newFileChannel(dataFile, StandardOpenOption.READ)) {
118121
final long fileSize = in.size();
119122
for (int offset = 0; offset < fileSize; ) {
@@ -122,7 +125,7 @@ public static MD5Hash computeMd5ForFile(File dataFile) throws IOException {
122125
offset += readSize;
123126
}
124127
}
125-
return new MD5Hash(digester.digest());
128+
return MD5Hash.newInstance(digester.digest());
126129
}
127130

128131
public static MD5Hash computeAndSaveMd5ForFile(File dataFile) {
@@ -147,7 +150,7 @@ public static MD5Hash computeAndSaveMd5ForFile(File dataFile) {
147150
*/
148151
public static void saveMD5File(File dataFile, MD5Hash digest)
149152
throws IOException {
150-
final String digestString = StringUtils.bytes2HexString(digest.getDigest());
153+
final String digestString = digest.toString();
151154
saveMD5File(dataFile, digestString);
152155
}
153156

@@ -162,10 +165,6 @@ private static void saveMD5File(File dataFile, String digestString)
162165
try (AtomicFileOutputStream afos = new AtomicFileOutputStream(md5File)) {
163166
afos.write(md5Line.getBytes(StandardCharsets.UTF_8));
164167
}
165-
166-
if (LOG.isDebugEnabled()) {
167-
LOG.debug("Saved MD5 " + digestString + " to " + md5File);
168-
}
169168
}
170169

171170
/**

0 commit comments

Comments
 (0)