Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f21b0ba
adding the StructuredMessageDecoder
gunjansingh-msft Oct 8, 2025
e1c23c5
adding the pipeline policy changes
gunjansingh-msft Oct 15, 2025
1d0cc92
smart retry changes
gunjansingh-msft Oct 29, 2025
b7ba234
fixing smart retry impl
gunjansingh-msft Dec 3, 2025
657f987
smart retry changes
gunjansingh-msft Dec 15, 2025
f7939e7
smart retry changes
gunjansingh-msft Dec 17, 2025
89f9292
smart retry changes
gunjansingh-msft Dec 17, 2025
62f72cc
smart retry changes
gunjansingh-msft Mar 16, 2026
09f49f9
adding content validation tests
gunjansingh-msft Mar 27, 2026
973cdf6
code refactoring
gunjansingh-msft Mar 28, 2026
a58dc63
fixing errors i introduced :(
ibrandes Mar 30, 2026
e12aa82
addressing review comments
gunjansingh-msft Apr 2, 2026
2b2e86d
code refactoring based on latest review comments
gunjansingh-msft Apr 3, 2026
11a2da2
code refactoring based on latest review comments
gunjansingh-msft Apr 8, 2026
c907649
simplifying retry mechanism
gunjansingh-msft Apr 8, 2026
5b153a4
removing dead code
gunjansingh-msft Apr 8, 2026
f72cb8b
addressing Kyle's review comments
gunjansingh-msft Apr 17, 2026
20e3303
addressing latest review comments
gunjansingh-msft Apr 17, 2026
b33a785
Merge origin/feature/storage/content-validation into feature/storage/…
gunjansingh-msft Apr 17, 2026
f9befff
refactoring based on latest review comments
gunjansingh-msft Apr 22, 2026
b3298c2
refactoring based on latest review comments
gunjansingh-msft Apr 22, 2026
ed8c3eb
refactoring based on latest review comments
gunjansingh-msft Apr 23, 2026
d7b0c51
refactoring based on latest review comments from kyle
gunjansingh-msft Apr 28, 2026
73f22e9
expanding test coverage
ibrandes Apr 28, 2026
c5f3a8c
removing unused imports
ibrandes Apr 28, 2026
2d5c9b8
recordings
ibrandes Apr 28, 2026
c1a3f1f
adding documentation to decoder classes
ibrandes Apr 29, 2026
3c77a76
small fixes and failure path tests
ibrandes Apr 29, 2026
aaa6414
addressing context comment
ibrandes Apr 29, 2026
e08eaba
analyze error
ibrandes Apr 29, 2026
a4393f5
removing close override from decodedresponse
ibrandes Apr 30, 2026
563c7dc
Merge branch 'feature/storage/content-validation' of https://github.c…
ibrandes Apr 30, 2026
73c0d25
line removal
ibrandes Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.azure.storage.common.policy.ResponseValidationPolicyBuilder;
import com.azure.storage.common.policy.ScrubEtagPolicy;
import com.azure.storage.common.policy.StorageBearerTokenChallengeAuthorizationPolicy;
import com.azure.storage.common.policy.StorageContentValidationDecoderPolicy;
import com.azure.storage.common.policy.StorageContentValidationPolicy;
import com.azure.storage.common.policy.StorageSharedKeyCredentialPolicy;

Expand Down Expand Up @@ -97,6 +98,8 @@ public static HttpPipeline buildPipeline(StorageSharedKeyCredential storageShare
// Closest to API goes first, closest to wire goes last.
List<HttpPipelinePolicy> policies = new ArrayList<>();

policies.add(new StorageContentValidationDecoderPolicy());
Comment thread
ibrandes marked this conversation as resolved.
Outdated

policies.add(getUserAgentPolicy(configuration, logOptions, clientOptions));
policies.add(new RequestIdPolicy());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.storage.blob.implementation.util;

import com.azure.core.util.Context;
import com.azure.storage.common.ContentValidationAlgorithm;
import com.azure.storage.common.implementation.Constants;
import com.azure.storage.common.implementation.contentvalidation.ContentValidationModeResolver;

/**
* Centralizes download content validation decisions based on {@link ContentValidationAlgorithm}.
* <p>
* Mirrors the pattern established by {@link ContentValidationModeResolver} for uploads.
* <p>
* RESERVED FOR INTERNAL USE.
*/
public final class DownloadValidationUtils {

private DownloadValidationUtils() {
}

/**
* Whether the algorithm requires structured message decoding (CRC64 / AUTO).
*/
public static boolean isStructuredMessageAlgorithm(ContentValidationAlgorithm algorithm) {
return ContentValidationModeResolver.isCrc64OrAuto(algorithm);
}
Comment thread
gunjansingh-msft marked this conversation as resolved.
Outdated

/**
* Resolves the effective algorithm, defaulting null to NONE.
*/
public static ContentValidationAlgorithm resolveAlgorithm(ContentValidationAlgorithm algorithm) {
return algorithm != null ? algorithm : ContentValidationAlgorithm.NONE;
}

/**
* Adds structured message decoding context key when CRC64/AUTO validation is active.
*
* @param context The base context to augment. Null is treated as {@link Context#NONE}.
* @param algorithm The resolved checksum algorithm.
* @return The augmented context.
*/
public static Context applyStructuredMessageContext(Context context, ContentValidationAlgorithm algorithm) {
Context base = context == null ? Context.NONE : context;
if (!isStructuredMessageAlgorithm(algorithm)) {
return base;
}
return base.addData(Constants.STRUCTURED_MESSAGE_DECODING_CONTEXT_KEY, true);
}
Comment thread
gunjansingh-msft marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import com.azure.storage.blob.implementation.util.BlobRequestConditionProperty;
import com.azure.storage.blob.implementation.util.BlobSasImplUtil;
import com.azure.storage.blob.implementation.util.ChunkedDownloadUtils;
import com.azure.storage.blob.implementation.util.DownloadValidationUtils;
import com.azure.storage.blob.implementation.util.ModelHelper;
import com.azure.storage.blob.models.AccessTier;
import com.azure.storage.blob.models.BlobBeginCopySourceRequestConditions;
Expand Down Expand Up @@ -1261,18 +1262,25 @@ Mono<BlobDownloadAsyncResponse> downloadStreamWithResponseInternal(BlobRange ran
BlobRequestConditions requestConditions, boolean getRangeContentMd5,
ContentValidationAlgorithm contentValidationAlgorithm, Context context) {
BlobRange finalRange = range == null ? new BlobRange(0) : range;
Boolean getMD5 = getRangeContentMd5 ? getRangeContentMd5 : null;

final ContentValidationAlgorithm algorithm
= DownloadValidationUtils.resolveAlgorithm(contentValidationAlgorithm);
final boolean isStructuredMessageEnabled = DownloadValidationUtils.isStructuredMessageAlgorithm(algorithm);

final Boolean finalGetMD5 = (!isStructuredMessageEnabled && getRangeContentMd5) ? true : null;

Comment thread
gunjansingh-msft marked this conversation as resolved.
Outdated
BlobRequestConditions finalRequestConditions
= requestConditions == null ? new BlobRequestConditions() : requestConditions;
DownloadRetryOptions finalOptions = (options == null) ? new DownloadRetryOptions() : options;

// The first range should eagerly convert headers as they'll be used to create response types.
Comment thread
gunjansingh-msft marked this conversation as resolved.
Context firstRangeContext = context == null
final Context baseContext = context == null
? new Context("azure-eagerly-convert-headers", true)
: context.addData("azure-eagerly-convert-headers", true);

return downloadRange(finalRange, finalRequestConditions, finalRequestConditions.getIfMatch(), getMD5,
firstRangeContext).map(response -> {
final Context downloadContext = DownloadValidationUtils.applyStructuredMessageContext(baseContext, algorithm);
Comment thread
gunjansingh-msft marked this conversation as resolved.
Outdated

return downloadRange(finalRange, finalRequestConditions, finalRequestConditions.getIfMatch(), finalGetMD5,
downloadContext).map(response -> {
BlobsDownloadHeaders blobsDownloadHeaders = new BlobsDownloadHeaders(response.getHeaders());
String eTag = blobsDownloadHeaders.getETag();
BlobDownloadHeaders blobDownloadHeaders = ModelHelper.populateBlobDownloadHeaders(blobsDownloadHeaders,
Expand All @@ -1298,31 +1306,33 @@ Mono<BlobDownloadAsyncResponse> downloadStreamWithResponseInternal(BlobRange ran
return Mono.error(throwable);
}

long newCount = finalCount - offset;

/*
* It's possible that the network stream will throw an error after emitting all data but before
* completing. Issuing a retry at this stage would leave the download in a bad state with
* incorrect count and offset values. Because we have read the intended amount of data, we can
* ignore the error at the end of the stream.
*/
if (newCount == 0) {
LOGGER.warning("Exception encountered in ReliableDownload after all data read from the network "
+ "but before stream signaled completion. Returning success as all data was downloaded. "
+ "Exception message: " + throwable.getMessage());
return Mono.empty();
Comment thread
ibrandes marked this conversation as resolved.
Comment thread
gunjansingh-msft marked this conversation as resolved.
}

try {
return downloadRange(new BlobRange(initialOffset + offset, newCount), finalRequestConditions,
eTag, getMD5, context);
long newCount = finalCount - offset;

/*
* It's possible that the network stream will throw an error after emitting all data but
* before completing. Issuing a retry at this stage would leave the download in a bad
* state with incorrect count and offset values. Because we have read the intended amount
* of data, we can ignore the error at the end of the stream.
*/
if (newCount == 0) {
LOGGER.warning(
"Exception encountered in ReliableDownload after all data read from the network "
+ "but before stream signaled completion. Returning success as all data was "
+ "downloaded. Exception message: " + throwable.getMessage());
return Mono.empty();
}

BlobRange retryRange = new BlobRange(initialOffset + offset, newCount);
return downloadRange(retryRange, finalRequestConditions, eTag, finalGetMD5, downloadContext);
Comment thread
ibrandes marked this conversation as resolved.
Outdated
} catch (Exception e) {
return Mono.error(e);
}
};

return BlobDownloadAsyncResponseConstructorProxy.create(response, onDownloadErrorResume, finalOptions);
});

Comment thread
gunjansingh-msft marked this conversation as resolved.
Outdated
}

private Mono<StreamResponse> downloadRange(BlobRange range, BlobRequestConditions requestConditions, String eTag,
Expand Down
Loading
Loading