cache = new ConcurrentHashMap<>();
+
+ @Override
+ public byte[] putChecksumValue(ChecksumAlgorithm algorithm, byte[] value) {
+ return cache.put(algorithm.algorithmId(), value);
+ }
+
+ @Override
+ public byte[] getChecksumValue(ChecksumAlgorithm algorithm) {
+ return cache.get(algorithm.algorithmId());
+ }
+
+ @Override
+ public boolean containsChecksumValue(ChecksumAlgorithm algorithm) {
+ return cache.containsKey(algorithm.algorithmId());
+ }
+}
diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/PayloadChecksumStore.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/PayloadChecksumStore.java
new file mode 100644
index 000000000000..f8c8a9d47c7d
--- /dev/null
+++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/PayloadChecksumStore.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.auth.spi.signer;
+
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
+import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultPayloadChecksumStore;
+
+/**
+ * Storage object for storing computed checksums for a request payload.
+ */
+@SdkProtectedApi
+public interface PayloadChecksumStore {
+ /**
+ * Store the checksum value computed using the given algorithm.
+ *
+ * @return The previous value stored for this algorithm or {@code null} if not present.
+ */
+ byte[] putChecksumValue(ChecksumAlgorithm algorithm, byte[] checksum);
+
+ /**
+ * Retrieve the stored checksum value for the given algorithm.
+ *
+ * @return The checksum value for the given algorithm or {@code null} if not present.
+ */
+ byte[] getChecksumValue(ChecksumAlgorithm algorithm);
+
+ /**
+ * Returns {@code true} if the store contains a checksum value for the given algorithm, {@code false} otherwise.
+ */
+ boolean containsChecksumValue(ChecksumAlgorithm algorithm);
+
+ /**
+ * Returns the default implementation of this interface.
+ */
+ static PayloadChecksumStore create() {
+ return new DefaultPayloadChecksumStore();
+ }
+}
diff --git a/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SdkInternalHttpSignerProperty.java b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SdkInternalHttpSignerProperty.java
new file mode 100644
index 000000000000..d95db35f6e46
--- /dev/null
+++ b/core/http-auth-spi/src/main/java/software/amazon/awssdk/http/auth/spi/signer/SdkInternalHttpSignerProperty.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.auth.spi.signer;
+
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+
+/**
+ * {@code HttpSigner} properties intended for use only by internal components of the SDK and SDK-provided implementations of
+ * this SPI.
+ */
+@SdkProtectedApi
+public final class SdkInternalHttpSignerProperty {
+
+ /**
+ * An object for storing checksums calculated for a payload.
+ *
+ * Note, checksums may not be relevant to some signers.
+ */
+ public static final SignerProperty CHECKSUM_STORE =
+ SignerProperty.create(SdkInternalHttpSignerProperty.class, "ChecksumStore");
+
+ private SdkInternalHttpSignerProperty() {
+ }
+}
diff --git a/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/PayloadChecksumStoreTest.java b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/PayloadChecksumStoreTest.java
new file mode 100644
index 000000000000..4ab329b0ee5d
--- /dev/null
+++ b/core/http-auth-spi/src/test/java/software/amazon/awssdk/http/auth/spi/signer/PayloadChecksumStoreTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.auth.spi.signer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
+import software.amazon.awssdk.http.auth.spi.internal.signer.DefaultPayloadChecksumStore;
+
+public class PayloadChecksumStoreTest {
+ private static final ChecksumAlgorithm ALGORITHM = () -> "crc32";
+
+ @Test
+ void putChecksumValue_noPreviousEntry_returnsNull() {
+ DefaultPayloadChecksumStore cache = new DefaultPayloadChecksumStore();
+
+ byte[] previous = cache.putChecksumValue(ALGORITHM, new byte[] {1, 2, 3});
+
+ assertThat(previous).isNull();
+ }
+
+ @Test
+ public void putChecksumValue_previousEntry_returnsValue() {
+ DefaultPayloadChecksumStore cache = new DefaultPayloadChecksumStore();
+
+ byte[] previous = {1, 2, 3};
+ cache.putChecksumValue(ALGORITHM, previous);
+
+ byte[] cached = cache.putChecksumValue(ALGORITHM, new byte[] {4, 5, 6});
+
+ assertThat(cached).isEqualTo(previous);
+ }
+
+ @Test
+ public void getChecksumValue_noEntry_returnsNull() {
+ DefaultPayloadChecksumStore cache = new DefaultPayloadChecksumStore();
+
+ assertThat(cache.getChecksumValue(ALGORITHM)).isNull();
+ }
+
+ @Test
+ public void getChecksumValue_hasEntry_returnsValue() {
+ DefaultPayloadChecksumStore cache = new DefaultPayloadChecksumStore();
+
+ byte[] value = {1, 2, 3};
+ cache.putChecksumValue(ALGORITHM, value);
+
+ assertThat(cache.getChecksumValue(ALGORITHM)).isEqualTo(value);
+ }
+
+ @Test
+ void containsChecksumValue_noEntry_returnsFalse() {
+ DefaultPayloadChecksumStore cache = new DefaultPayloadChecksumStore();
+ assertThat(cache.containsChecksumValue(ALGORITHM)).isFalse();
+ }
+
+ @Test
+ void containsChecksumValue_hasEntry_returnsValue() {
+ DefaultPayloadChecksumStore cache = new DefaultPayloadChecksumStore();
+ byte[] value = {1, 2, 3};
+ cache.putChecksumValue(ALGORITHM, value);
+ assertThat(cache.containsChecksumValue(ALGORITHM)).isTrue();
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java
index bd09c48d9f43..3fe0f69d3ab8 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java
@@ -36,6 +36,8 @@
import software.amazon.awssdk.http.SdkHttpExecutionAttributes;
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider;
+import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
import software.amazon.awssdk.identity.spi.IdentityProviders;
import software.amazon.awssdk.utils.AttributeMap;
@@ -204,6 +206,12 @@ public final class SdkInternalExecutionAttribute extends SdkExecutionAttribute {
public static final ExecutionAttribute TOKEN_CONFIGURED_FROM_ENV = new ExecutionAttribute<>(
"TokenConfiguredFromEnv");
+ /**
+ * The store used by {@link HttpSigner} implementations to store payload checksums.
+ */
+ public static final ExecutionAttribute CHECKSUM_STORE =
+ new ExecutionAttribute<>("ChecksumStore");
+
/**
* The backing attribute for RESOLVED_CHECKSUM_SPECS.
* This holds the real ChecksumSpecs value, and is used to map to the ChecksumAlgorithm signer property
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBody.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBody.java
index 2226c67fcdc9..c903829d07e2 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBody.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBody.java
@@ -34,7 +34,9 @@
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.core.checksums.Algorithm;
import software.amazon.awssdk.core.exception.SdkException;
+import software.amazon.awssdk.core.internal.checksums.NoOpPayloadChecksumStore;
import software.amazon.awssdk.core.internal.util.HttpChecksumUtils;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
import software.amazon.awssdk.utils.BinaryUtils;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.async.DelegatingSubscriber;
@@ -55,6 +57,7 @@ public class ChecksumCalculatingAsyncRequestBody implements AsyncRequestBody {
private final ChecksumAlgorithm algorithm;
private final String trailerHeader;
private final long totalBytes;
+ private final PayloadChecksumStore payloadChecksumStore;
private ChecksumCalculatingAsyncRequestBody(DefaultBuilder builder) {
@@ -66,6 +69,7 @@ private ChecksumCalculatingAsyncRequestBody(DefaultBuilder builder) {
this.sdkChecksum = builder.algorithm != null ? SdkChecksum.forAlgorithm(algorithm) : null;
this.trailerHeader = builder.trailerHeader;
this.totalBytes = initTotalBytes(wrapped, builder.contentLengthHeader);
+ this.payloadChecksumStore = builder.checksumStore != null ? builder.checksumStore : NoOpPayloadChecksumStore.create();
}
static long initTotalBytes(AsyncRequestBody wrapped, Long contentLengthHeader) {
@@ -118,6 +122,8 @@ public interface Builder extends SdkBuilder s) {
SynchronousChunkBuffer synchronousChunkBuffer = new SynchronousChunkBuffer(totalBytes);
alwaysInvokeOnNext(wrapped.flatMapIterable(synchronousChunkBuffer::buffer))
- .subscribe(new ChecksumCalculatingSubscriber(s, sdkChecksum, trailerHeader, totalBytes));
+ .subscribe(new ChecksumCalculatingSubscriber(s,
+ algorithm,
+ sdkChecksum,
+ payloadChecksumStore,
+ trailerHeader,
+ totalBytes));
}
private SdkPublisher alwaysInvokeOnNext(SdkPublisher source) {
@@ -192,17 +210,24 @@ private SdkPublisher alwaysInvokeOnNext(SdkPublisher sou
private static final class ChecksumCalculatingSubscriber implements Subscriber {
private final Subscriber super ByteBuffer> wrapped;
+ private final ChecksumAlgorithm algorithm;
private final SdkChecksum checksum;
+ private final PayloadChecksumStore checksumStore;
private final String trailerHeader;
private byte[] checksumBytes;
private final AtomicLong remainingBytes;
private Subscription subscription;
ChecksumCalculatingSubscriber(Subscriber super ByteBuffer> wrapped,
+ ChecksumAlgorithm algorithm,
SdkChecksum checksum,
- String trailerHeader, long totalBytes) {
+ PayloadChecksumStore checksumStore,
+ String trailerHeader,
+ long totalBytes) {
this.wrapped = wrapped;
+ this.algorithm = algorithm;
this.checksum = checksum;
+ this.checksumStore = checksumStore;
this.trailerHeader = trailerHeader;
this.remainingBytes = new AtomicLong(totalBytes);
}
@@ -223,7 +248,11 @@ public void onNext(ByteBuffer byteBuffer) {
byteBuffer.reset();
}
if (lastByte && checksumBytes == null && checksum != null) {
- checksumBytes = checksum.getChecksumBytes();
+ checksumBytes = checksumStore.getChecksumValue(algorithm);
+ if (checksumBytes == null) {
+ checksumBytes = checksum.getChecksumBytes();
+ checksumStore.putChecksumValue(algorithm, checksumBytes);
+ }
ByteBuffer allocatedBuffer = getFinalChecksumAppendedChunk(byteBuffer);
wrapped.onNext(allocatedBuffer);
} else if (byteBuffer.hasRemaining()) {
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/checksums/NoOpPayloadChecksumStore.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/checksums/NoOpPayloadChecksumStore.java
new file mode 100644
index 000000000000..68c4b23cc687
--- /dev/null
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/checksums/NoOpPayloadChecksumStore.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.core.internal.checksums;
+
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
+
+@SdkInternalApi
+public final class NoOpPayloadChecksumStore implements PayloadChecksumStore {
+ private NoOpPayloadChecksumStore() {
+ }
+
+ @Override
+ public byte[] putChecksumValue(ChecksumAlgorithm algorithm, byte[] checksum) {
+ return null;
+ }
+
+ @Override
+ public byte[] getChecksumValue(ChecksumAlgorithm algorithm) {
+ return null;
+ }
+
+ @Override
+ public boolean containsChecksumValue(ChecksumAlgorithm algorithm) {
+ return false;
+ }
+
+ public static NoOpPayloadChecksumStore create() {
+ return new NoOpPayloadChecksumStore();
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java
index d7bb3c699fb5..ca4b4d8f7f2c 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStage.java
@@ -22,6 +22,7 @@
import static software.amazon.awssdk.core.HttpChecksumConstant.SIGNING_METHOD;
import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS;
import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.AUTH_SCHEMES;
+import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.CHECKSUM_STORE;
import static software.amazon.awssdk.core.internal.io.AwsChunkedInputStream.DEFAULT_CHUNK_SIZE;
import static software.amazon.awssdk.core.internal.util.ChunkContentUtils.calculateChecksumTrailerLength;
import static software.amazon.awssdk.core.internal.util.ChunkContentUtils.calculateStreamContentLength;
@@ -54,6 +55,7 @@
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
import software.amazon.awssdk.utils.BinaryUtils;
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.awssdk.utils.Md5Utils;
@@ -75,6 +77,8 @@ public HttpChecksumStage(ClientType clientType) {
public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, RequestExecutionContext context)
throws Exception {
+ ensurePayloadChecksumStorePresent(context.executionAttributes());
+
if (sraSigningEnabled(context)) {
return sraChecksum(request, context);
}
@@ -84,9 +88,10 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re
private SdkHttpFullRequest.Builder legacyChecksum(SdkHttpFullRequest.Builder request, RequestExecutionContext context) {
ChecksumSpecs resolvedChecksumSpecs = getResolvedChecksumSpecs(context.executionAttributes());
+ PayloadChecksumStore checksumStore = getPayloadChecksumStore(context.executionAttributes());
if (md5ChecksumRequired(request, context)) {
- addMd5ChecksumInHeader(request);
+ addMd5ChecksumInHeader(request, checksumStore);
return request;
}
@@ -96,7 +101,7 @@ private SdkHttpFullRequest.Builder legacyChecksum(SdkHttpFullRequest.Builder req
}
if (flexibleChecksumInHeaderRequired(context, resolvedChecksumSpecs)) {
- addFlexibleChecksumInHeader(request, context, resolvedChecksumSpecs);
+ addFlexibleChecksumInHeader(request, context, resolvedChecksumSpecs, checksumStore);
return request;
}
@@ -171,10 +176,14 @@ private boolean md5ChecksumRequired(SdkHttpFullRequest.Builder request, RequestE
* request body to use that buffered content. We obviously don't want to do that for giant streams, so we haven't opted to do
* that yet.
*/
- private void addMd5ChecksumInHeader(SdkHttpFullRequest.Builder request) {
+ private void addMd5ChecksumInHeader(SdkHttpFullRequest.Builder request, PayloadChecksumStore checksumStore) {
try {
- String payloadMd5 = Md5Utils.md5AsBase64(request.contentStreamProvider().newStream());
- request.putHeader(Header.CONTENT_MD5, payloadMd5);
+ byte[] payloadMd5 = checksumStore.getChecksumValue(DefaultChecksumAlgorithm.MD5);
+ if (payloadMd5 == null) {
+ payloadMd5 = Md5Utils.computeMD5Hash(request.contentStreamProvider().newStream());
+ checksumStore.putChecksumValue(DefaultChecksumAlgorithm.MD5, payloadMd5);
+ }
+ request.putHeader(Header.CONTENT_MD5, BinaryUtils.toBase64(payloadMd5));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
@@ -234,7 +243,11 @@ private void addFlexibleChecksumInTrailer(SdkHttpFullRequest.Builder request, Re
int chunkSize = 0;
if (clientType == ClientType.SYNC) {
- request.contentStreamProvider(new ChecksumCalculatingStreamProvider(request.contentStreamProvider(), checksumSpecs));
+ request.contentStreamProvider(
+ new ChecksumCalculatingStreamProvider(request.contentStreamProvider(),
+ checksumSpecs,
+ getPayloadChecksumStore(context.executionAttributes())
+ ));
originalContentLength =
context.executionContext().interceptorContext().requestBody().get().optionalContentLength().orElse(0L);
chunkSize = DEFAULT_CHUNK_SIZE;
@@ -244,6 +257,7 @@ private void addFlexibleChecksumInTrailer(SdkHttpFullRequest.Builder request, Re
ChecksumCalculatingAsyncRequestBody.builder()
.asyncRequestBody(context.requestProvider())
.algorithm(checksumSpecs.algorithmV2())
+ .checksumStore(getPayloadChecksumStore(context.executionAttributes()))
.trailerHeader(checksumSpecs.headerName());
Optional maybeContentLengthHeader = request.firstMatchingHeader("Content-Length")
.map(Long::parseLong);
@@ -307,32 +321,55 @@ private boolean flexibleChecksumInHeaderRequired(RequestExecutionContext context
* that yet.
*/
private void addFlexibleChecksumInHeader(SdkHttpFullRequest.Builder request, RequestExecutionContext context,
- ChecksumSpecs checksumSpecs) {
+ ChecksumSpecs checksumSpecs, PayloadChecksumStore checksumStore) {
try {
Algorithm legacyAlgorithm = checksumSpecs.algorithm();
- String payloadChecksum = BinaryUtils.toBase64(HttpChecksumUtils.computeChecksum(
- context.executionContext().interceptorContext().requestBody().get().contentStreamProvider().newStream(),
- legacyAlgorithm));
- request.putHeader(checksumSpecs.headerName(), payloadChecksum);
+ ChecksumAlgorithm newAlgorithm = HttpChecksumUtils.toNewChecksumAlgorithm(legacyAlgorithm);
+ byte[] payloadChecksum = checksumStore.getChecksumValue(newAlgorithm);
+ if (payloadChecksum == null) {
+ payloadChecksum = HttpChecksumUtils.computeChecksum(
+ context.executionContext().interceptorContext().requestBody().get().contentStreamProvider().newStream(),
+ legacyAlgorithm);
+ checksumStore.putChecksumValue(newAlgorithm, payloadChecksum);
+ }
+ String headerValue = BinaryUtils.toBase64(payloadChecksum);
+ request.putHeader(checksumSpecs.headerName(), headerValue);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
+ private void ensurePayloadChecksumStorePresent(ExecutionAttributes executionAttributes) {
+ PayloadChecksumStore cache = getPayloadChecksumStore(executionAttributes);
+ if (cache == null) {
+ cache = PayloadChecksumStore.create();
+ executionAttributes.putAttribute(CHECKSUM_STORE, cache);
+ }
+ }
+
+ private PayloadChecksumStore getPayloadChecksumStore(ExecutionAttributes executionAttributes) {
+ return executionAttributes.getAttribute(CHECKSUM_STORE);
+ }
+
static final class ChecksumCalculatingStreamProvider implements ContentStreamProvider {
private final ContentStreamProvider underlyingInputStreamProvider;
private final String checksumHeaderForTrailer;
private final ChecksumSpecs checksumSpecs;
+ private final PayloadChecksumStore checksumStore;
private InputStream currentStream;
+ private final ChecksumAlgorithm checksumAlgorithm;
private software.amazon.awssdk.core.checksums.SdkChecksum sdkChecksum;
ChecksumCalculatingStreamProvider(ContentStreamProvider underlyingInputStreamProvider,
- ChecksumSpecs checksumSpecs) {
+ ChecksumSpecs checksumSpecs,
+ PayloadChecksumStore checksumStore) {
this.underlyingInputStreamProvider = underlyingInputStreamProvider;
this.sdkChecksum = software.amazon.awssdk.core.checksums.SdkChecksum.forAlgorithm(
checksumSpecs.algorithm());
+ this.checksumAlgorithm = HttpChecksumUtils.toNewChecksumAlgorithm(checksumSpecs.algorithm());
this.checksumHeaderForTrailer = checksumSpecs.headerName();
this.checksumSpecs = checksumSpecs;
+ this.checksumStore = checksumStore;
}
@Override
@@ -340,7 +377,9 @@ public InputStream newStream() {
closeCurrentStream();
currentStream = AwsUnsignedChunkedEncodingInputStream.builder()
.inputStream(underlyingInputStreamProvider.newStream())
+ .checksumAlgorithm(checksumAlgorithm)
.sdkChecksum(sdkChecksum)
+ .checksumStore(checksumStore)
.checksumHeaderForTrailer(checksumHeaderForTrailer)
.build();
return currentStream;
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java
index d92628c2bba2..3d3240c3d4d8 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java
@@ -35,6 +35,8 @@
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
+import software.amazon.awssdk.http.auth.spi.signer.SdkInternalHttpSignerProperty;
import software.amazon.awssdk.http.auth.spi.signer.SignRequest;
import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
import software.amazon.awssdk.identity.spi.Identity;
@@ -89,8 +91,12 @@ private SdkHttpFullRequest sraSignRequest(SdkHttpFullReques
CompletableFuture extends T> identityFuture = selectedAuthScheme.identity();
T identity = CompletableFutureUtils.joinLikeSync(identityFuture);
+ // Should not be null, added by HttpChecksumStage for SRA signed requests
+ PayloadChecksumStore payloadChecksumStore =
+ context.executionAttributes().getAttribute(SdkInternalExecutionAttribute.CHECKSUM_STORE);
+
Pair measuredSign = MetricUtils.measureDuration(
- () -> doSraSign(request, selectedAuthScheme, identity));
+ () -> doSraSign(request, selectedAuthScheme, identity, payloadChecksumStore));
context.attemptMetricCollector().reportMetric(CoreMetric.SIGNING_DURATION, measuredSign.right());
SdkHttpFullRequest signedRequest = measuredSign.left();
@@ -100,10 +106,12 @@ private SdkHttpFullRequest sraSignRequest(SdkHttpFullReques
private SdkHttpFullRequest doSraSign(SdkHttpFullRequest request,
SelectedAuthScheme selectedAuthScheme,
- T identity) {
+ T identity,
+ PayloadChecksumStore payloadChecksumStore) {
SignRequest.Builder signRequestBuilder = SignRequest
.builder(identity)
.putProperty(HttpSigner.SIGNING_CLOCK, signingClock())
+ .putProperty(SdkInternalHttpSignerProperty.CHECKSUM_STORE, payloadChecksumStore)
.request(request)
.payload(request.contentStreamProvider().orElse(null));
AuthSchemeOption authSchemeOption = selectedAuthScheme.authSchemeOption();
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsChunkedEncodingInputStream.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsChunkedEncodingInputStream.java
index ec4870f5e686..0b98608a67e7 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsChunkedEncodingInputStream.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsChunkedEncodingInputStream.java
@@ -20,8 +20,11 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
import software.amazon.awssdk.core.checksums.SdkChecksum;
+import software.amazon.awssdk.core.internal.checksums.NoOpPayloadChecksumStore;
import software.amazon.awssdk.core.internal.chunked.AwsChunkedEncodingConfig;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
import software.amazon.awssdk.utils.Validate;
/**
@@ -45,7 +48,9 @@ public abstract class AwsChunkedEncodingInputStream extends AwsChunkedInputStrea
protected boolean isTrailingTerminated = true;
private final int chunkSize;
private final int maxBufferSize;
+ private final ChecksumAlgorithm checksumAlgorithm;
private final SdkChecksum sdkChecksum;
+ private final PayloadChecksumStore checksumStore;
private boolean isLastTrailingCrlf;
/**
@@ -58,7 +63,10 @@ public abstract class AwsChunkedEncodingInputStream extends AwsChunkedInputStrea
* See {@link AwsChunkedEncodingConfig} for default values.
*/
protected AwsChunkedEncodingInputStream(InputStream in,
- SdkChecksum sdkChecksum, String checksumHeaderForTrailer,
+ ChecksumAlgorithm checksumAlgorithm,
+ SdkChecksum sdkChecksum,
+ PayloadChecksumStore checksumStore,
+ String checksumHeaderForTrailer,
AwsChunkedEncodingConfig config) {
AwsChunkedEncodingConfig awsChunkedEncodingConfig = config == null ? AwsChunkedEncodingConfig.create() : config;
@@ -78,14 +86,18 @@ protected AwsChunkedEncodingInputStream(InputStream in,
if (maxBufferSize < chunkSize) {
throw new IllegalArgumentException("Max buffer size should not be less than chunk size");
}
+ this.checksumAlgorithm = checksumAlgorithm;
this.sdkChecksum = sdkChecksum;
+ this.checksumStore = checksumStore == null ? NoOpPayloadChecksumStore.create() : checksumStore;
this.checksumHeaderForTrailer = checksumHeaderForTrailer;
}
protected abstract static class Builder {
protected InputStream inputStream;
+ protected ChecksumAlgorithm checksumAlgorithm;
protected SdkChecksum sdkChecksum;
+ protected PayloadChecksumStore checksumStore;
protected String checksumHeaderForTrailer;
protected AwsChunkedEncodingConfig awsChunkedEncodingConfig;
@@ -110,6 +122,11 @@ public T awsChunkedEncodingConfig(AwsChunkedEncodingConfig awsChunkedEncodingCon
return (T) this;
}
+ public T checksumAlgorithm(ChecksumAlgorithm checksumAlgorithm) {
+ this.checksumAlgorithm = checksumAlgorithm;
+ return (T) this;
+ }
+
/**
*
* @param sdkChecksum Instance of SdkChecksum, this can be null if we do not want to calculate Checksum
@@ -120,6 +137,11 @@ public T sdkChecksum(SdkChecksum sdkChecksum) {
return (T) this;
}
+ public T checksumStore(PayloadChecksumStore checksumStore) {
+ this.checksumStore = checksumStore;
+ return (T) this;
+ }
+
/**
*
* @param checksumHeaderForTrailer String value of Trailer header where checksum will be updated.
@@ -166,7 +188,11 @@ private boolean setUpTrailingChunks() {
return true;
}
if (calculatedChecksum == null) {
- calculatedChecksum = sdkChecksum.getChecksumBytes();
+ calculatedChecksum = checksumStore.getChecksumValue(checksumAlgorithm);
+ if (calculatedChecksum == null) {
+ calculatedChecksum = sdkChecksum.getChecksumBytes();
+ checksumStore.putChecksumValue(checksumAlgorithm, calculatedChecksum);
+ }
currentChunkIterator = new ChunkContentIterator(createChecksumChunkHeader());
return false;
} else if (!isLastTrailingCrlf) {
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsUnsignedChunkedEncodingInputStream.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsUnsignedChunkedEncodingInputStream.java
index 4c7f46a248cf..14b1cab5bf6c 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsUnsignedChunkedEncodingInputStream.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/io/AwsUnsignedChunkedEncodingInputStream.java
@@ -18,9 +18,11 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.checksums.spi.ChecksumAlgorithm;
import software.amazon.awssdk.core.checksums.SdkChecksum;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.internal.chunked.AwsChunkedEncodingConfig;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
import software.amazon.awssdk.utils.BinaryUtils;
/**
@@ -29,10 +31,13 @@
@SdkInternalApi
public class AwsUnsignedChunkedEncodingInputStream extends AwsChunkedEncodingInputStream {
- private AwsUnsignedChunkedEncodingInputStream(InputStream in, AwsChunkedEncodingConfig awsChunkedEncodingConfig,
+ private AwsUnsignedChunkedEncodingInputStream(InputStream in,
+ AwsChunkedEncodingConfig awsChunkedEncodingConfig,
+ ChecksumAlgorithm checksumAlgorithm,
SdkChecksum sdkChecksum,
+ PayloadChecksumStore checksumStore,
String checksumHeaderForTrailer) {
- super(in, sdkChecksum, checksumHeaderForTrailer, awsChunkedEncodingConfig);
+ super(in, checksumAlgorithm, sdkChecksum, checksumStore, checksumHeaderForTrailer, awsChunkedEncodingConfig);
}
public static Builder builder() {
@@ -85,8 +90,12 @@ protected byte[] createChecksumChunkHeader() {
public static final class Builder extends AwsChunkedEncodingInputStream.Builder {
public AwsUnsignedChunkedEncodingInputStream build() {
return new AwsUnsignedChunkedEncodingInputStream(
- this.inputStream, this.awsChunkedEncodingConfig,
- this.sdkChecksum, this.checksumHeaderForTrailer);
+ this.inputStream,
+ this.awsChunkedEncodingConfig,
+ this.checksumAlgorithm,
+ this.sdkChecksum,
+ this.checksumStore,
+ this.checksumHeaderForTrailer);
}
}
}
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBodyTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBodyTest.java
index eddc94da63ed..c17d2c3fdaab 100644
--- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBodyTest.java
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/async/ChecksumCalculatingAsyncRequestBodyTest.java
@@ -15,6 +15,7 @@
package software.amazon.awssdk.core.internal.async;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -42,6 +43,7 @@
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.internal.util.Mimetype;
import software.amazon.awssdk.http.async.SimpleSubscriber;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
import software.amazon.awssdk.utils.BinaryUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -301,6 +303,61 @@ public void explicit0ContentLength_containsEmptyStringTrailingChecksum(TestCase
assertThat(sb.toString()).isEqualTo(expectedEmptyString);
}
+ @Test
+ void subscribe_checksumStoreContainsChecksumValue_reusesValue() {
+ byte[] content = "Hello world".getBytes(StandardCharsets.UTF_8);
+ AsyncRequestBody body = AsyncRequestBody.fromBytes(content);
+
+ byte[] checksumValue = "my-checksum".getBytes(StandardCharsets.UTF_8);
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+ store.putChecksumValue(DefaultChecksumAlgorithm.CRC32, checksumValue);
+
+ String trailerHeader = "x-amz-checksum-crc32";
+ ChecksumCalculatingAsyncRequestBody checksumBody =
+ ChecksumCalculatingAsyncRequestBody.builder()
+ .contentLengthHeader((long) content.length)
+ .trailerHeader(trailerHeader)
+ .algorithm(DefaultChecksumAlgorithm.CRC32)
+ .checksumStore(store)
+ .asyncRequestBody(body)
+ .build();
+
+ String encoded = toString(checksumBody);
+
+ assertThat(encoded).endsWith(String.format("%s:%s\r\n\r\n", trailerHeader, BinaryUtils.toBase64(checksumValue)));
+ }
+
+ @Test
+ void subscribe_checksumStoreEmpty_storesComputedValue() {
+ byte[] content = "Hello world".getBytes(StandardCharsets.UTF_8);
+ AsyncRequestBody body = AsyncRequestBody.fromBytes(content);
+
+ String expectedChecksum = "i9aeUg==";
+
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+
+ String trailerHeader = "x-amz-checksum-crc32";
+ ChecksumCalculatingAsyncRequestBody checksumBody =
+ ChecksumCalculatingAsyncRequestBody.builder()
+ .contentLengthHeader((long) content.length)
+ .trailerHeader(trailerHeader)
+ .algorithm(DefaultChecksumAlgorithm.CRC32)
+ .checksumStore(store)
+ .asyncRequestBody(body)
+ .build();
+
+ String encoded = toString(checksumBody);
+
+ assertThat(encoded).endsWith(String.format("%s:%s\r\n\r\n", trailerHeader, expectedChecksum));
+ assertThat(store.getChecksumValue(DefaultChecksumAlgorithm.CRC32)).isEqualTo(BinaryUtils.fromBase64(expectedChecksum));
+ }
+
+ private static String toString(Publisher publisher) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Flowable.fromPublisher(publisher).blockingForEach(chunk -> baos.write(BinaryUtils.copyAllBytesFrom(chunk)));
+ return new String(baos.toByteArray(), StandardCharsets.UTF_8);
+ }
+
static class EmptyBufferPublisher implements AsyncRequestBody {
private final ByteBuffer[] buffers = new ByteBuffer[2];
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageNonSraTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageNonSraTest.java
index 0e1b9efe07e1..a099e91ab6ce 100644
--- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageNonSraTest.java
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageNonSraTest.java
@@ -23,6 +23,7 @@
import static software.amazon.awssdk.http.Header.CONTENT_LENGTH;
import static software.amazon.awssdk.http.Header.CONTENT_MD5;
+import java.nio.charset.StandardCharsets;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@@ -41,6 +42,10 @@
import software.amazon.awssdk.core.signer.NoOpSigner;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.http.SdkHttpFullRequest;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
+import software.amazon.awssdk.utils.BinaryUtils;
+import software.amazon.awssdk.utils.IoUtils;
import utils.ValidSdkObjects;
@RunWith(MockitoJUnitRunner.class)
@@ -48,6 +53,8 @@ public class HttpChecksumStageNonSraTest {
private static final String CHECKSUM_SPECS_HEADER = "x-amz-checksum-sha256";
private static final RequestBody REQUEST_BODY = RequestBody.fromString("TestBody");
private static final AsyncRequestBody ASYNC_REQUEST_BODY = AsyncRequestBody.fromString("TestBody");
+ private static final String PAYLOAD_CHECKSUM_SHA256 = "/T5YuTxNWthvWXg+TJMwl60XKcAnLMrrOZe/jA9Y+eI=";
+
private final HttpChecksumStage syncStage = new HttpChecksumStage(ClientType.SYNC);
private final HttpChecksumStage asyncStage = new HttpChecksumStage(ClientType.ASYNC);
@@ -69,6 +76,40 @@ public void sync_md5Required_addsMd5Checksum_doesNotAddFlexibleChecksums() throw
assertThat(requestBuilder.firstMatchingHeader(CHECKSUM_SPECS_HEADER)).isEmpty();
}
+ @Test
+ public void sync_md5Required_checksumValueInStore_usesExistingValue() throws Exception {
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+ boolean isAsyncStreaming = false;
+ RequestExecutionContext ctx = md5RequiredRequestContext(isAsyncStreaming);
+
+ byte[] checksumValue = "my-md5".getBytes(StandardCharsets.UTF_8);
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+ store.putChecksumValue(DefaultChecksumAlgorithm.MD5, checksumValue);
+
+ ctx.executionAttributes().putAttribute(SdkInternalExecutionAttribute.CHECKSUM_STORE, store);
+
+ syncStage.execute(requestBuilder, ctx);
+
+ assertThat(requestBuilder.headers().get(CONTENT_MD5)).containsExactly(BinaryUtils.toBase64(checksumValue));
+ }
+
+ @Test
+ public void sync_md5Required_checksumStoreEmpty_storesComputedMd5() throws Exception {
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+ boolean isAsyncStreaming = false;
+ RequestExecutionContext ctx = md5RequiredRequestContext(isAsyncStreaming);
+
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+ ctx.executionAttributes().putAttribute(SdkInternalExecutionAttribute.CHECKSUM_STORE, store);
+
+ syncStage.execute(requestBuilder, ctx);
+
+ String expectedChecksum = "9dzKaiLL99all2ZyHa76RA==";
+
+ assertThat(requestBuilder.headers().get(CONTENT_MD5)).containsExactly(expectedChecksum);
+ assertThat(store.getChecksumValue(DefaultChecksumAlgorithm.MD5)).isEqualTo(BinaryUtils.fromBase64(expectedChecksum));
+ }
+
@Test
public void async_nonStreaming_md5Required_addsMd5Checksum_doesNotAddFlexibleChecksums() throws Exception {
SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
@@ -119,6 +160,40 @@ public void syncWithCustomSigner_flexibleChecksumInTrailerRequired_addsFlexibleC
assertThat(requestBuilder.firstMatchingHeader(CHECKSUM_SPECS_HEADER)).isEmpty();
}
+ @Test
+ public void syncWithCustomSigner_flexibleChecksumInTrailerRequired_storeEmpty_storesComputedValue() throws Exception {
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+
+ RequestExecutionContext ctx = noOpSignerRequestContext(ClientType.SYNC);
+
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+ ctx.executionAttributes().putAttribute(SdkInternalExecutionAttribute.CHECKSUM_STORE, store);
+
+ syncStage.execute(requestBuilder, ctx);
+
+ String content = IoUtils.toUtf8String(requestBuilder.build().contentStreamProvider().get().newStream());
+ assertThat(getTrailingChecksum(content)).isEqualTo(String.format("%s:%s", CHECKSUM_SPECS_HEADER, PAYLOAD_CHECKSUM_SHA256));
+ assertThat(store.getChecksumValue(DefaultChecksumAlgorithm.SHA256)).isEqualTo(BinaryUtils.fromBase64(PAYLOAD_CHECKSUM_SHA256));
+ }
+
+ @Test
+ public void syncWithCustomSigner_flexibleChecksumInTrailerRequired_checksumValueInStore_usesExistingValue() throws Exception {
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+
+ RequestExecutionContext ctx = noOpSignerRequestContext(ClientType.SYNC);
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+ byte[] checksumValue = "my-sha256".getBytes(StandardCharsets.UTF_8);
+ store.putChecksumValue(DefaultChecksumAlgorithm.SHA256, checksumValue);
+
+ ctx.executionAttributes().putAttribute(SdkInternalExecutionAttribute.CHECKSUM_STORE, store);
+
+ syncStage.execute(requestBuilder, ctx);
+
+ String content = IoUtils.toUtf8String(requestBuilder.build().contentStreamProvider().get().newStream());
+ assertThat(getTrailingChecksum(content)).isEqualTo(String.format("%s:%s", CHECKSUM_SPECS_HEADER,
+ BinaryUtils.toBase64(checksumValue)));
+ }
+
@Test
public void asyncWithCustomSigner_flexibleChecksumInTrailerRequired_addsFlexibleChecksumInTrailer() throws Exception {
SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
@@ -181,7 +256,7 @@ public void sync_flexibleChecksumInHeaderRequired_addsFlexibleChecksumInHeader_d
syncStage.execute(requestBuilder, ctx);
- assertThat(requestBuilder.headers().get(CHECKSUM_SPECS_HEADER)).containsExactly("/T5YuTxNWthvWXg+TJMwl60XKcAnLMrrOZe/jA9Y+eI=");
+ assertThat(requestBuilder.headers().get(CHECKSUM_SPECS_HEADER)).containsExactly(PAYLOAD_CHECKSUM_SHA256);
assertThat(requestBuilder.firstMatchingHeader(HEADER_FOR_TRAILER_REFERENCE)).isEmpty();
assertThat(requestBuilder.firstMatchingHeader("Content-encoding")).isEmpty();
@@ -191,6 +266,44 @@ public void sync_flexibleChecksumInHeaderRequired_addsFlexibleChecksumInHeader_d
assertThat(requestBuilder.firstMatchingHeader(CONTENT_MD5)).isEmpty();
}
+ @Test
+ public void sync_flexibleChecksumInHeaderRequired_checksumValueInStore_usesExistingValue() throws Exception {
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+ boolean isStreaming = false;
+
+ RequestExecutionContext ctx = syncFlexibleChecksumRequiredRequestContext(isStreaming);
+
+ byte[] checksumValue = "my-sha256".getBytes(StandardCharsets.UTF_8);
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+ // Test context uses SHA-256 as the flexible checksum
+ store.putChecksumValue(DefaultChecksumAlgorithm.SHA256, checksumValue);
+
+ ctx.executionAttributes().putAttribute(SdkInternalExecutionAttribute.CHECKSUM_STORE, store);
+
+ syncStage.execute(requestBuilder, ctx);
+
+ assertThat(requestBuilder.headers().get(CHECKSUM_SPECS_HEADER)).containsExactly(BinaryUtils.toBase64(checksumValue));
+
+ }
+
+ @Test
+ public void sync_flexibleChecksumInHeaderRequired_checksumStoreEmpty_storesComputedSha256() throws Exception {
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+ boolean isStreaming = false;
+
+ RequestExecutionContext ctx = syncFlexibleChecksumRequiredRequestContext(isStreaming);
+
+ PayloadChecksumStore store = PayloadChecksumStore.create();
+
+ ctx.executionAttributes().putAttribute(SdkInternalExecutionAttribute.CHECKSUM_STORE, store);
+
+ syncStage.execute(requestBuilder, ctx);
+
+ assertThat(requestBuilder.headers().get(CHECKSUM_SPECS_HEADER)).containsExactly(PAYLOAD_CHECKSUM_SHA256);
+ // Test context uses SHA-256 as the flexible checksum
+ assertThat(store.getChecksumValue(DefaultChecksumAlgorithm.SHA256)).isEqualTo(BinaryUtils.fromBase64(PAYLOAD_CHECKSUM_SHA256));
+ }
+
@Test
public void async_flexibleChecksumInHeaderRequired_addsFlexibleChecksumInHeader_doesNotAddMd5ChecksumAndFlexibleChecksumInTrailer() throws Exception {
SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
@@ -199,7 +312,7 @@ public void async_flexibleChecksumInHeaderRequired_addsFlexibleChecksumInHeader_
asyncStage.execute(requestBuilder, ctx);
- assertThat(requestBuilder.headers().get(CHECKSUM_SPECS_HEADER)).containsExactly("/T5YuTxNWthvWXg+TJMwl60XKcAnLMrrOZe/jA9Y+eI=");
+ assertThat(requestBuilder.headers().get(CHECKSUM_SPECS_HEADER)).containsExactly(PAYLOAD_CHECKSUM_SHA256);
assertThat(requestBuilder.firstMatchingHeader(HEADER_FOR_TRAILER_REFERENCE)).isEmpty();
assertThat(requestBuilder.firstMatchingHeader("Content-encoding")).isEmpty();
@@ -209,8 +322,21 @@ public void async_flexibleChecksumInHeaderRequired_addsFlexibleChecksumInHeader_
assertThat(requestBuilder.firstMatchingHeader(CONTENT_MD5)).isEmpty();
}
+ private static String getTrailingChecksum(String payload) {
+ for (String line : payload.split("\r\n")) {
+ if (line.startsWith("x-amz-checksum")) {
+ return line;
+ }
+ }
+ return null;
+ }
+
private SdkHttpFullRequest.Builder createHttpRequestBuilder() {
- return SdkHttpFullRequest.builder().contentStreamProvider(REQUEST_BODY.contentStreamProvider());
+ return SdkHttpFullRequest.builder()
+ .method(SdkHttpMethod.GET)
+ .protocol("https")
+ .host("sdk.aws")
+ .contentStreamProvider(REQUEST_BODY.contentStreamProvider());
}
private RequestExecutionContext md5RequiredRequestContext(boolean isAsyncStreaming) {
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageSraTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageSraTest.java
index e9fc44d106bb..ed166597a441 100644
--- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageSraTest.java
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/HttpChecksumStageSraTest.java
@@ -20,6 +20,7 @@
import static software.amazon.awssdk.core.HttpChecksumConstant.SIGNING_METHOD;
import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.RESOLVED_CHECKSUM_SPECS;
import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.AUTH_SCHEMES;
+import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.CHECKSUM_STORE;
import static software.amazon.awssdk.core.internal.signer.SigningMethod.UNSIGNED_PAYLOAD;
import static software.amazon.awssdk.http.Header.CONTENT_LENGTH;
import static software.amazon.awssdk.http.Header.CONTENT_MD5;
@@ -44,6 +45,7 @@
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
import utils.ValidSdkObjects;
public class HttpChecksumStageSraTest {
@@ -143,6 +145,31 @@ public void async_flexibleChecksumInTrailer_addsFlexibleChecksumInTrailer() thro
assertThat(requestBuilder.firstMatchingHeader(CHECKSUM_SPECS_HEADER)).isEmpty();
}
+ @Test
+ public void execute_checksumStoreAttributeNotPresent_shouldCreate() throws Exception {
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+ RequestExecutionContext ctx =
+ flexibleChecksumRequestContext(ClientType.SYNC, ChecksumSpecs.builder().isRequestChecksumRequired(true), false);
+
+ new HttpChecksumStage(ClientType.SYNC).execute(requestBuilder, ctx);
+
+ assertThat(ctx.executionAttributes().getAttribute(CHECKSUM_STORE)).isNotNull();
+ }
+
+ @Test
+ public void execute_checksumStoreAttributePresent_shouldNotOverwrite() throws Exception {
+ PayloadChecksumStore cache = PayloadChecksumStore.create();
+
+ SdkHttpFullRequest.Builder requestBuilder = createHttpRequestBuilder();
+ RequestExecutionContext ctx =
+ flexibleChecksumRequestContext(ClientType.SYNC, ChecksumSpecs.builder().isRequestChecksumRequired(true), false);
+ ctx.executionAttributes().putAttribute(CHECKSUM_STORE, cache);
+
+ new HttpChecksumStage(ClientType.SYNC).execute(requestBuilder, ctx);
+
+ assertThat(ctx.executionAttributes().getAttribute(CHECKSUM_STORE)).isSameAs(cache);
+ }
+
private SdkHttpFullRequest.Builder createHttpRequestBuilder() {
return SdkHttpFullRequest.builder().contentStreamProvider(REQUEST_BODY.contentStreamProvider());
}
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStageTest.java
index 865535a6298d..d86ba2c70e5b 100644
--- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStageTest.java
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStageTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static software.amazon.awssdk.core.interceptor.SdkExecutionAttribute.TIME_OFFSET;
+import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.CHECKSUM_STORE;
import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME;
import static software.amazon.awssdk.core.metrics.CoreMetric.SIGNING_DURATION;
@@ -54,6 +55,8 @@
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
+import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore;
+import software.amazon.awssdk.http.auth.spi.signer.SdkInternalHttpSignerProperty;
import software.amazon.awssdk.http.auth.spi.signer.SignRequest;
import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
import software.amazon.awssdk.http.auth.spi.signer.SignerProperty;
@@ -372,6 +375,35 @@ public void execute_selectedAuthScheme_signer_doesPreSraSign() throws Exception
verifyNoInteractions(httpSigner);
}
+ @Test
+ public void execute_checksumStoreAttributePresent_propagatesChecksumStoreToSigner() throws Exception {
+ SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>(
+ CompletableFuture.completedFuture(identity),
+ httpSigner,
+ AuthSchemeOption.builder()
+ .schemeId("my.auth#myAuth")
+ .putSignerProperty(SIGNER_PROPERTY, "value")
+ .build());
+ RequestExecutionContext context = createContext(selectedAuthScheme, null);
+
+ PayloadChecksumStore cache = PayloadChecksumStore.create();
+ context.executionAttributes().putAttribute(CHECKSUM_STORE, cache);
+
+ SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build();
+ when(httpSigner.sign(ArgumentMatchers.>any()))
+ .thenReturn(SignedRequest.builder()
+ .request(signedRequest)
+ .build());
+
+ SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build();
+ stage.execute(request, context);
+
+ ArgumentCaptor> signRequestCaptor = ArgumentCaptor.forClass(SignRequest.class);
+ verify(httpSigner).sign(signRequestCaptor.capture());
+
+ assertThat(signRequestCaptor.getValue().property(SdkInternalHttpSignerProperty.CHECKSUM_STORE)).isSameAs(cache);
+ }
+
private RequestExecutionContext createContext(SelectedAuthScheme selectedAuthScheme, Signer oldSigner) {
SdkRequest sdkRequest = ValidSdkObjects.sdkRequest();
InterceptorContext interceptorContext =
diff --git a/services/s3/pom.xml b/services/s3/pom.xml
index 73d3b2a75b87..89c0ed167db7 100644
--- a/services/s3/pom.xml
+++ b/services/s3/pom.xml
@@ -158,6 +158,12 @@
${awsjavasdk.version}
test