Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: implementations of FromHexString() for md5 and crc32c #246

Merged
merged 1 commit into from Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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