Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix: implementations of FromHexString() for md5 and crc32c (#246)
  • Loading branch information
frankyn committed Apr 14, 2020
1 parent 7824c15 commit c9b23b3
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 12 deletions.
Expand Up @@ -34,8 +34,8 @@
import com.google.common.io.BaseEncoding;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -232,6 +232,7 @@ public abstract static class Builder {
*
* @see <a href="https://cloud.google.com/storage/docs/hashes-etags#_JSONAPI">Hashes and ETags:
* Best Practices</a>
* @throws IllegalArgumentException when given an invalid hexadecimal value.
*/
public abstract Builder setMd5FromHexString(String md5HexString);

Expand All @@ -252,6 +253,7 @@ public abstract static class Builder {
*
* @see <a href="https://cloud.google.com/storage/docs/hashes-etags#_JSONAPI">Hashes and ETags:
* Best Practices</a>
* @throws IllegalArgumentException when given an invalid hexadecimal value.
*/
public abstract Builder setCrc32cFromHexString(String crc32cHexString);

Expand Down Expand Up @@ -293,7 +295,7 @@ public abstract static class Builder {
}

static final class BuilderImpl extends Builder {

private final String hexDecimalValues = "0123456789abcdef";
private BlobId blobId;
private String generatedId;
private String contentType;
Expand Down Expand Up @@ -442,16 +444,27 @@ public Builder setMd5(String md5) {
return this;
}

@Override
public Builder setMd5FromHexString(String md5HexString) {
if (md5HexString == null) {
return this;
}
byte[] bytes = new BigInteger(md5HexString, 16).toByteArray();
int leadingEmptyBytes = bytes.length - md5HexString.length() / 2;
if (leadingEmptyBytes > 0) {
bytes = Arrays.copyOfRange(bytes, leadingEmptyBytes, bytes.length);
if (md5HexString.length() % 2 != 0) {
throw new IllegalArgumentException(
"each byte must be represented by 2 valid hexadecimal characters");
}
String md5HexStringLower = md5HexString.toLowerCase();
ByteBuffer md5ByteBuffer = ByteBuffer.allocate(md5HexStringLower.length() / 2);
for (int charIndex = 0; charIndex < md5HexStringLower.length(); charIndex += 2) {
int higherOrderBits = this.hexDecimalValues.indexOf(md5HexStringLower.charAt(charIndex));
int lowerOrderBits = this.hexDecimalValues.indexOf(md5HexStringLower.charAt(charIndex + 1));
if (higherOrderBits == -1 || lowerOrderBits == -1) {
throw new IllegalArgumentException(
"each byte must be represented by 2 valid hexadecimal characters");
}
md5ByteBuffer.put((byte) (higherOrderBits << 4 | lowerOrderBits));
}
this.md5 = BaseEncoding.base64().encode(bytes);
this.md5 = BaseEncoding.base64().encode(md5ByteBuffer.array());
return this;
}

Expand All @@ -466,12 +479,23 @@ public Builder setCrc32cFromHexString(String crc32cHexString) {
if (crc32cHexString == null) {
return this;
}
byte[] bytes = new BigInteger(crc32cHexString, 16).toByteArray();
int leadingEmptyBytes = bytes.length - crc32cHexString.length() / 2;
if (leadingEmptyBytes > 0) {
bytes = Arrays.copyOfRange(bytes, leadingEmptyBytes, bytes.length);
if (crc32cHexString.length() % 2 != 0) {
throw new IllegalArgumentException(
"each byte must be represented by 2 valid hexadecimal characters");
}
String crc32cHexStringLower = crc32cHexString.toLowerCase();
ByteBuffer crc32cByteBuffer = ByteBuffer.allocate(crc32cHexStringLower.length() / 2);
for (int charIndex = 0; charIndex < crc32cHexStringLower.length(); charIndex += 2) {
int higherOrderBits = this.hexDecimalValues.indexOf(crc32cHexStringLower.charAt(charIndex));
int lowerOrderBits =
this.hexDecimalValues.indexOf(crc32cHexStringLower.charAt(charIndex + 1));
if (higherOrderBits == -1 || lowerOrderBits == -1) {
throw new IllegalArgumentException(
"each byte must be represented by 2 valid hexadecimal characters");
}
crc32cByteBuffer.put((byte) (higherOrderBits << 4 | lowerOrderBits));
}
this.crc32c = BaseEncoding.base64().encode(bytes);
this.crc32c = BaseEncoding.base64().encode(crc32cByteBuffer.array());
return this;
}

Expand Down
Expand Up @@ -49,12 +49,16 @@ public class BlobInfoTest {
private static final String CONTENT_LANGUAGE = "En";
private static final String CRC32 = "FF00";
private static final String CRC32_HEX_STRING = "145d34";
private static final String CRC32_HEX_STRING_LEADING_ZEROS = "005d34";
private static final String CRC32_BASE64_LEADING_ZEROS = "AF00";
private static final Long DELETE_TIME = System.currentTimeMillis();
private static final String ETAG = "0xFF00";
private static final Long GENERATION = 1L;
private static final String GENERATED_ID = "B/N:1";
private static final String MD5 = "FF00";
private static final String MD5_HEX_STRING = "145d34";
private static final String MD5_HEX_STRING_LEADING_ZEROS = "0006a7de52b4e0b82602ce09809523ca";
private static final String MD5_BASE64_LEADING_ZEROS = "AAan3lK04LgmAs4JgJUjyg==";
private static final String MEDIA_LINK = "http://media/b/n";
private static final Map<String, String> METADATA = ImmutableMap.of("n1", "v1", "n2", "v2");
private static final Long META_GENERATION = 10L;
Expand Down Expand Up @@ -132,13 +136,31 @@ public void testToBuilderSetMd5FromHexString() {
assertEquals(MD5, blobInfo.getMd5());
}

@Test
public void testToBuilderSetMd5FromHexStringLeadingZeros() {
BlobInfo blobInfo =
BlobInfo.newBuilder(BlobId.of("b2", "n2"))
.setMd5FromHexString(MD5_HEX_STRING_LEADING_ZEROS)
.build();
assertEquals(MD5_BASE64_LEADING_ZEROS, blobInfo.getMd5());
}

@Test
public void testToBuilderSetCrc32cFromHexString() {
BlobInfo blobInfo =
BlobInfo.newBuilder(BlobId.of("b2", "n2")).setCrc32cFromHexString(CRC32_HEX_STRING).build();
assertEquals(CRC32, blobInfo.getCrc32c());
}

@Test
public void testToBuilderSetCrc32cFromHexStringLeadingZeros() {
BlobInfo blobInfo =
BlobInfo.newBuilder(BlobId.of("b2", "n2"))
.setCrc32cFromHexString(CRC32_HEX_STRING_LEADING_ZEROS)
.build();
assertEquals(CRC32_BASE64_LEADING_ZEROS, blobInfo.getCrc32c());
}

@Test
public void testToBuilderIncomplete() {
BlobInfo incompleteBlobInfo = BlobInfo.newBuilder(BlobId.of("b2", "n2")).build();
Expand Down

0 comments on commit c9b23b3

Please sign in to comment.