Skip to content

Commit bd40324

Browse files
Dhriti07Dhriti Chopra
andauthored
feat(storage): Adding CumulativeHasher wrapper class for Full object … (#13239)
Adding CumulativeHasher wrapper class for Full object checksum Refer to: go/full_checksum_java --------- Co-authored-by: Dhriti Chopra <dhritichopra@google.com>
1 parent 8e91704 commit bd40324

2 files changed

Lines changed: 161 additions & 2 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.api.gax.grpc.GrpcStatusCode;
20+
import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown;
21+
import com.google.protobuf.ByteString;
22+
import com.google.storage.v2.Object;
23+
import io.grpc.Status.Code;
24+
import java.nio.ByteBuffer;
25+
import java.util.Locale;
26+
import java.util.OptionalLong;
27+
import java.util.function.Supplier;
28+
29+
/**
30+
* A wrapper around hasher that accumulates checksums and validates them at the end of the read if
31+
* it was a full object read.
32+
*/
33+
final class CumulativeHasher implements Hasher {
34+
private final Hasher delegate;
35+
private final long startOffset;
36+
private final OptionalLong limit;
37+
private Crc32cLengthKnown cumulativeHash;
38+
39+
CumulativeHasher(Hasher delegate, long startOffset, OptionalLong limit) {
40+
this.delegate = delegate;
41+
this.startOffset = startOffset;
42+
this.limit = limit;
43+
this.cumulativeHash = Crc32cValue.zero();
44+
}
45+
46+
@Override
47+
public Crc32cLengthKnown hash(ByteBuffer b) {
48+
return delegate.hash(b);
49+
}
50+
51+
@Override
52+
public Crc32cLengthKnown hash(ByteString byteString) {
53+
return delegate.hash(byteString);
54+
}
55+
56+
@Override
57+
public void validate(Crc32cValue<?> expected, Supplier<ByteBuffer> b)
58+
throws ChecksumMismatchException {
59+
ByteBuffer byteBuffer = b.get();
60+
Crc32cLengthKnown actual = delegate.hash(byteBuffer);
61+
if (actual != null) {
62+
if (expected != null && !actual.eqValue(expected)) {
63+
throw new ChecksumMismatchException(expected, actual);
64+
}
65+
accumulate(actual);
66+
}
67+
}
68+
69+
@Override
70+
public void validate(Crc32cValue<?> expected, ByteString byteString)
71+
throws ChecksumMismatchException {
72+
Crc32cLengthKnown actual = delegate.hash(byteString);
73+
if (actual != null) {
74+
if (expected != null && !actual.eqValue(expected)) {
75+
throw new ChecksumMismatchException(expected, actual);
76+
}
77+
accumulate(actual);
78+
}
79+
}
80+
81+
@Override
82+
public void validateUnchecked(Crc32cValue<?> expected, ByteString byteString)
83+
throws UncheckedChecksumMismatchException {
84+
Crc32cLengthKnown actual = delegate.hash(byteString);
85+
if (actual != null) {
86+
if (expected != null && !actual.eqValue(expected)) {
87+
throw new UncheckedChecksumMismatchException(expected, actual);
88+
}
89+
accumulate(actual);
90+
}
91+
}
92+
93+
@Override
94+
public <C extends Crc32cValue<?>> C nullSafeConcat(C r1, Crc32cLengthKnown r2) {
95+
return delegate.nullSafeConcat(r1, r2);
96+
}
97+
98+
@Override
99+
public Crc32cLengthKnown initialValue() {
100+
return delegate.initialValue();
101+
}
102+
103+
// Checks if it was a full object read.
104+
boolean qualifiesForVerification(Object metadata) {
105+
return startOffset == 0
106+
&& metadata != null
107+
&& metadata.hasChecksums()
108+
&& metadata.getChecksums().hasCrc32C()
109+
&& (!limit.isPresent() || limit.getAsLong() >= metadata.getSize());
110+
}
111+
112+
void validateCumulativeChecksum(Object metadata)
113+
throws UncheckedCumulativeChecksumMismatchException {
114+
if (qualifiesForVerification(metadata)) {
115+
Crc32cValue<?> expected = Crc32cValue.of(metadata.getChecksums().getCrc32C());
116+
Crc32cLengthKnown actual = getCumulativeHash();
117+
if (!actual.eqValue(expected)) {
118+
throw new UncheckedCumulativeChecksumMismatchException(expected, actual);
119+
}
120+
}
121+
}
122+
123+
private void accumulate(Crc32cLengthKnown actual) {
124+
cumulativeHash = cumulativeHash.concat(actual);
125+
}
126+
127+
Crc32cLengthKnown getCumulativeHash() {
128+
return cumulativeHash;
129+
}
130+
}
131+
132+
final class UncheckedCumulativeChecksumMismatchException
133+
extends com.google.api.gax.rpc.DataLossException {
134+
private static final GrpcStatusCode STATUS_CODE = GrpcStatusCode.of(Code.DATA_LOSS);
135+
private final Crc32cValue<?> expected;
136+
private final Crc32cLengthKnown actual;
137+
138+
UncheckedCumulativeChecksumMismatchException(Crc32cValue<?> expected, Crc32cLengthKnown actual) {
139+
super(
140+
String.format(
141+
Locale.US,
142+
"Mismatch cumulative checksum value. Expected %s actual %s",
143+
expected.debugString(),
144+
actual.debugString()),
145+
/* cause= */ null,
146+
STATUS_CODE,
147+
/* retryable= */ false);
148+
this.expected = expected;
149+
this.actual = actual;
150+
}
151+
152+
Crc32cValue<?> getExpected() {
153+
return expected;
154+
}
155+
156+
Crc32cLengthKnown getActual() {
157+
return actual;
158+
}
159+
}

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/Hasher.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ final class ChecksumMismatchException extends IOException {
212212
private final Crc32cValue<?> expected;
213213
private final Crc32cLengthKnown actual;
214214

215-
private ChecksumMismatchException(Crc32cValue<?> expected, Crc32cLengthKnown actual) {
215+
ChecksumMismatchException(Crc32cValue<?> expected, Crc32cLengthKnown actual) {
216216
super(
217217
String.format(
218218
Locale.US,
@@ -237,7 +237,7 @@ final class UncheckedChecksumMismatchException extends DataLossException {
237237
private final Crc32cValue<?> expected;
238238
private final Crc32cLengthKnown actual;
239239

240-
private UncheckedChecksumMismatchException(Crc32cValue<?> expected, Crc32cLengthKnown actual) {
240+
UncheckedChecksumMismatchException(Crc32cValue<?> expected, Crc32cLengthKnown actual) {
241241
super(
242242
String.format(
243243
"Mismatch checksum value. Expected %s actual %s",

0 commit comments

Comments
 (0)