diff --git a/sdk/storage/azure-storage-blob/assets.json b/sdk/storage/azure-storage-blob/assets.json index 8cad139f33ff..1279a1ae962b 100644 --- a/sdk/storage/azure-storage-blob/assets.json +++ b/sdk/storage/azure-storage-blob/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/storage/azure-storage-blob", - "Tag": "java/storage/azure-storage-blob_47f4243e59" + "Tag": "java/storage/azure-storage-blob_48aa31792b" } diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/AppendBlobItemConstructorProxy.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/AppendBlobItemConstructorProxy.java new file mode 100644 index 000000000000..b62fc2ecf1da --- /dev/null +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/AppendBlobItemConstructorProxy.java @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.implementation.accesshelpers; + +import com.azure.storage.blob.models.AppendBlobItem; + +import java.time.OffsetDateTime; + +/** + * Helper class to access private values of {@link AppendBlobItem} across package boundaries. + */ +public final class AppendBlobItemConstructorProxy { + private static AppendBlobItemConstructorAccessor accessor; + + private AppendBlobItemConstructorProxy() { + } + + /** + * Type defining the methods to set the non-public properties of an {@link AppendBlobItem}. + */ + public interface AppendBlobItemConstructorAccessor { + /** + * Creates a new instance of {@link AppendBlobItem}. + * + * @param eTag ETag of the append blob. + * @param lastModified Last modified time of the append blob. + * @param contentMd5 Content MD5 of the append blob. + * @param isServerEncrypted Flag indicating if the append blob is encrypted on the server. + * @param encryptionKeySha256 The encryption key used to encrypt the append blob. + * @param encryptionScope The encryption scope used to encrypt the append blob. + * @param blobAppendOffset The offset at which the block was committed to the append blob. + * @param blobCommittedBlockCount The number of committed blocks in the append blob. + * @param versionId The version identifier of the append blob. + * @param contentCrc64 Content CRC64 of the append blob. + * @return A new instance of {@link AppendBlobItem}. + */ + AppendBlobItem create(String eTag, OffsetDateTime lastModified, byte[] contentMd5, boolean isServerEncrypted, + String encryptionKeySha256, String encryptionScope, String blobAppendOffset, + Integer blobCommittedBlockCount, String versionId, byte[] contentCrc64); + } + + /** + * The method called from {@link AppendBlobItem} to set its accessor. + * + * @param accessor The accessor. + */ + public static void setAccessor(final AppendBlobItemConstructorAccessor accessor) { + AppendBlobItemConstructorProxy.accessor = accessor; + } + + /** + * Creates a new instance of {@link AppendBlobItem}. + * + * @param eTag ETag of the append blob. + * @param lastModified Last modified time of the append blob. + * @param contentMd5 Content MD5 of the append blob. + * @param isServerEncrypted Flag indicating if the append blob is encrypted on the server. + * @param encryptionKeySha256 The encryption key used to encrypt the append blob. + * @param encryptionScope The encryption scope used to encrypt the append blob. + * @param blobAppendOffset The offset at which the block was committed to the append blob. + * @param blobCommittedBlockCount The number of committed blocks in the append blob. + * @param versionId The version identifier of the append blob. + * @param contentCrc64 Content CRC64 of the append blob. + * @return A new instance of {@link AppendBlobItem}. + */ + public static AppendBlobItem create(String eTag, OffsetDateTime lastModified, byte[] contentMd5, + boolean isServerEncrypted, String encryptionKeySha256, String encryptionScope, String blobAppendOffset, + Integer blobCommittedBlockCount, String versionId, byte[] contentCrc64) { + // This looks odd but is necessary, it is possible to engage the access helper before anywhere else in the + // application accesses AppendBlobItem which triggers the accessor to be configured. So, if the accessor is null + // this effectively pokes the class to set up the accessor. + if (accessor == null) { + new AppendBlobItem(null, null, null, false, null, null, null, null, null); + } + + assert accessor != null; + return accessor.create(eTag, lastModified, contentMd5, isServerEncrypted, encryptionKeySha256, encryptionScope, + blobAppendOffset, blobCommittedBlockCount, versionId, contentCrc64); + } +} diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/BlockBlobItemConstructorProxy.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/BlockBlobItemConstructorProxy.java new file mode 100644 index 000000000000..0d973e425bea --- /dev/null +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/BlockBlobItemConstructorProxy.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.implementation.accesshelpers; + +import com.azure.storage.blob.models.BlockBlobItem; + +import java.time.OffsetDateTime; + +/** + * Helper class to access private values of {@link BlockBlobItem} across package boundaries. + */ +public final class BlockBlobItemConstructorProxy { + private static BlockBlobItemConstructorAccessor accessor; + + private BlockBlobItemConstructorProxy() { + } + + /** + * Type defining the methods to set the non-public properties of a {@link BlockBlobItem}. + */ + public interface BlockBlobItemConstructorAccessor { + /** + * Creates a new instance of {@link BlockBlobItem}. + * + * @param eTag ETag of the block blob. + * @param lastModified Last modified time of the block blob. + * @param contentMd5 Content MD5 of the block blob. + * @param isServerEncrypted Flag indicating if the block blob is encrypted on the server. + * @param encryptionKeySha256 The encryption key used to encrypt the block blob. + * @param encryptionScope The encryption scope used to encrypt the block blob. + * @param versionId The version identifier of the block blob. + * @param contentCrc64 Content CRC64 of the block blob. + * @return A new instance of {@link BlockBlobItem}. + */ + BlockBlobItem create(String eTag, OffsetDateTime lastModified, byte[] contentMd5, Boolean isServerEncrypted, + String encryptionKeySha256, String encryptionScope, String versionId, byte[] contentCrc64); + } + + /** + * The method called from {@link BlockBlobItem} to set its accessor. + * + * @param accessor The accessor. + */ + public static void setAccessor(final BlockBlobItemConstructorAccessor accessor) { + BlockBlobItemConstructorProxy.accessor = accessor; + } + + /** + * Creates a new instance of {@link BlockBlobItem}. + * + * @param eTag ETag of the block blob. + * @param lastModified Last modified time of the block blob. + * @param contentMd5 Content MD5 of the block blob. + * @param isServerEncrypted Flag indicating if the block blob is encrypted on the server. + * @param encryptionKeySha256 The encryption key used to encrypt the block blob. + * @param encryptionScope The encryption scope used to encrypt the block blob. + * @param versionId The version identifier of the block blob. + * @param contentCrc64 Content CRC64 of the block blob. + * @return A new instance of {@link BlockBlobItem}. + */ + public static BlockBlobItem create(String eTag, OffsetDateTime lastModified, byte[] contentMd5, + Boolean isServerEncrypted, String encryptionKeySha256, String encryptionScope, String versionId, + byte[] contentCrc64) { + + if (accessor == null) { + new BlockBlobItem(null, null, null, (Boolean) null, null, null, null); + } + + assert accessor != null; + return accessor.create(eTag, lastModified, contentMd5, isServerEncrypted, encryptionKeySha256, encryptionScope, + versionId, contentCrc64); + } +} diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/PageBlobItemConstructorProxy.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/PageBlobItemConstructorProxy.java new file mode 100644 index 000000000000..fb241d47a268 --- /dev/null +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/accesshelpers/PageBlobItemConstructorProxy.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.blob.implementation.accesshelpers; + +import com.azure.storage.blob.models.PageBlobItem; + +import java.time.OffsetDateTime; + +/** + * Helper class to access private values of {@link PageBlobItem} across package boundaries. + */ +public final class PageBlobItemConstructorProxy { + private static PageBlobItemConstructorAccessor accessor; + + private PageBlobItemConstructorProxy() { + } + + /** + * Type defining the methods to set the non-public properties of a {@link PageBlobItem}. + */ + public interface PageBlobItemConstructorAccessor { + /** + * Creates a new instance of {@link PageBlobItem}. + * + * @param eTag ETag of the page blob. + * @param lastModified Last modified time of the page blob. + * @param contentMd5 Content MD5 of the page blob. + * @param isServerEncrypted Flag indicating if the page blob is encrypted on the server. + * @param encryptionKeySha256 The encryption key used to encrypt the page blob. + * @param encryptionScope The encryption scope used to encrypt the page blob. + * @param blobSequenceNumber The current sequence number for the page blob. + * @param versionId The version identifier of the page blob. + * @param contentCrc64 Content CRC64 of the page blob. + * @return A new instance of {@link PageBlobItem}. + */ + PageBlobItem create(String eTag, OffsetDateTime lastModified, byte[] contentMd5, Boolean isServerEncrypted, + String encryptionKeySha256, String encryptionScope, Long blobSequenceNumber, String versionId, + byte[] contentCrc64); + } + + /** + * The method called from {@link PageBlobItem} to set its accessor. + * + * @param accessor The accessor. + */ + public static void setAccessor(final PageBlobItemConstructorAccessor accessor) { + PageBlobItemConstructorProxy.accessor = accessor; + } + + /** + * Creates a new instance of {@link PageBlobItem}. + * + * @param eTag ETag of the page blob. + * @param lastModified Last modified time of the page blob. + * @param contentMd5 Content MD5 of the page blob. + * @param isServerEncrypted Flag indicating if the page blob is encrypted on the server. + * @param encryptionKeySha256 The encryption key used to encrypt the page blob. + * @param encryptionScope The encryption scope used to encrypt the page blob. + * @param blobSequenceNumber The current sequence number for the page blob. + * @param versionId The version identifier of the page blob. + * @param contentCrc64 Content CRC64 of the page blob. + * @return A new instance of {@link PageBlobItem}. + */ + public static PageBlobItem create(String eTag, OffsetDateTime lastModified, byte[] contentMd5, + Boolean isServerEncrypted, String encryptionKeySha256, String encryptionScope, Long blobSequenceNumber, + String versionId, byte[] contentCrc64) { + + if (accessor == null) { + new PageBlobItem(null, null, null, false, null, null, null, null); + } + + assert accessor != null; + return accessor.create(eTag, lastModified, contentMd5, isServerEncrypted, encryptionKeySha256, encryptionScope, + blobSequenceNumber, versionId, contentCrc64); + } +} diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsPutBlobFromUrlHeaders.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsPutBlobFromUrlHeaders.java index 9e8fb72f68db..02c212f1e3fa 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsPutBlobFromUrlHeaders.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsPutBlobFromUrlHeaders.java @@ -36,6 +36,12 @@ public final class BlockBlobsPutBlobFromUrlHeaders { @Generated private byte[] contentMD5; + /* + * The x-ms-content-crc64 property. + */ + @Generated + private byte[] xMsContentCrc64; + /* * The x-ms-client-request-id property. */ @@ -84,6 +90,8 @@ public final class BlockBlobsPutBlobFromUrlHeaders { @Generated private String xMsEncryptionScope; + private static final HttpHeaderName X_MS_CONTENT_CRC64 = HttpHeaderName.fromString("x-ms-content-crc64"); + private static final HttpHeaderName X_MS_VERSION = HttpHeaderName.fromString("x-ms-version"); private static final HttpHeaderName X_MS_VERSION_ID = HttpHeaderName.fromString("x-ms-version-id"); @@ -116,6 +124,12 @@ public BlockBlobsPutBlobFromUrlHeaders(HttpHeaders rawHeaders) { } else { this.contentMD5 = null; } + String xMsContentCrc64 = rawHeaders.getValue(X_MS_CONTENT_CRC64); + if (xMsContentCrc64 != null) { + this.xMsContentCrc64 = Base64.getDecoder().decode(xMsContentCrc64); + } else { + this.xMsContentCrc64 = null; + } this.xMsClientRequestId = rawHeaders.getValue(HttpHeaderName.X_MS_CLIENT_REQUEST_ID); this.xMsRequestId = rawHeaders.getValue(HttpHeaderName.X_MS_REQUEST_ID); this.xMsVersion = rawHeaders.getValue(X_MS_VERSION); @@ -209,6 +223,28 @@ public BlockBlobsPutBlobFromUrlHeaders setContentMD5(byte[] contentMD5) { return this; } + /** + * Get the xMsContentCrc64 property: The x-ms-content-crc64 property. + * + * @return the xMsContentCrc64 value. + */ + @Generated + public byte[] getXMsContentCrc64() { + return CoreUtils.clone(this.xMsContentCrc64); + } + + /** + * Set the xMsContentCrc64 property: The x-ms-content-crc64 property. + * + * @param xMsContentCrc64 the xMsContentCrc64 value to set. + * @return the BlockBlobsPutBlobFromUrlHeaders object itself. + */ + @Generated + public BlockBlobsPutBlobFromUrlHeaders setXMsContentCrc64(byte[] xMsContentCrc64) { + this.xMsContentCrc64 = CoreUtils.clone(xMsContentCrc64); + return this; + } + /** * Get the xMsClientRequestId property: The x-ms-client-request-id property. * diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsUploadHeaders.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsUploadHeaders.java index fb8da8e12407..63fa2cd2212d 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsUploadHeaders.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/models/BlockBlobsUploadHeaders.java @@ -36,6 +36,12 @@ public final class BlockBlobsUploadHeaders { @Generated private byte[] contentMD5; + /* + * The x-ms-content-crc64 property. + */ + @Generated + private byte[] xMsContentCrc64; + /* * The x-ms-client-request-id property. */ @@ -90,6 +96,8 @@ public final class BlockBlobsUploadHeaders { @Generated private String xMsStructuredBody; + private static final HttpHeaderName X_MS_CONTENT_CRC64 = HttpHeaderName.fromString("x-ms-content-crc64"); + private static final HttpHeaderName X_MS_VERSION = HttpHeaderName.fromString("x-ms-version"); private static final HttpHeaderName X_MS_VERSION_ID = HttpHeaderName.fromString("x-ms-version-id"); @@ -124,6 +132,12 @@ public BlockBlobsUploadHeaders(HttpHeaders rawHeaders) { } else { this.contentMD5 = null; } + String xMsContentCrc64 = rawHeaders.getValue(X_MS_CONTENT_CRC64); + if (xMsContentCrc64 != null) { + this.xMsContentCrc64 = Base64.getDecoder().decode(xMsContentCrc64); + } else { + this.xMsContentCrc64 = null; + } this.xMsClientRequestId = rawHeaders.getValue(HttpHeaderName.X_MS_CLIENT_REQUEST_ID); this.xMsRequestId = rawHeaders.getValue(HttpHeaderName.X_MS_REQUEST_ID); this.xMsVersion = rawHeaders.getValue(X_MS_VERSION); @@ -218,6 +232,28 @@ public BlockBlobsUploadHeaders setContentMD5(byte[] contentMD5) { return this; } + /** + * Get the xMsContentCrc64 property: The x-ms-content-crc64 property. + * + * @return the xMsContentCrc64 value. + */ + @Generated + public byte[] getXMsContentCrc64() { + return CoreUtils.clone(this.xMsContentCrc64); + } + + /** + * Set the xMsContentCrc64 property: The x-ms-content-crc64 property. + * + * @param xMsContentCrc64 the xMsContentCrc64 value to set. + * @return the BlockBlobsUploadHeaders object itself. + */ + @Generated + public BlockBlobsUploadHeaders setXMsContentCrc64(byte[] xMsContentCrc64) { + this.xMsContentCrc64 = CoreUtils.clone(xMsContentCrc64); + return this; + } + /** * Get the xMsClientRequestId property: The x-ms-client-request-id property. * diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/AppendBlobItem.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/AppendBlobItem.java index 8acbf7de2813..57f8c54faee4 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/AppendBlobItem.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/AppendBlobItem.java @@ -5,6 +5,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.util.CoreUtils; +import com.azure.storage.blob.implementation.accesshelpers.AppendBlobItemConstructorProxy; import java.time.OffsetDateTime; @@ -22,6 +23,11 @@ public class AppendBlobItem { private final String blobAppendOffset; private final Integer blobCommittedBlockCount; private final String versionId; + private final byte[] contentCrc64; + + static { + AppendBlobItemConstructorProxy.setAccessor(AppendBlobItem::new); + } /** * Constructs an {@link AppendBlobItem}. @@ -76,6 +82,14 @@ public AppendBlobItem(final String eTag, final OffsetDateTime lastModified, fina public AppendBlobItem(final String eTag, final OffsetDateTime lastModified, final byte[] contentMd5, final boolean isServerEncrypted, final String encryptionKeySha256, final String encryptionScope, final String blobAppendOffset, final Integer blobCommittedBlockCount, final String versionId) { + this(eTag, lastModified, contentMd5, isServerEncrypted, encryptionKeySha256, encryptionScope, blobAppendOffset, + blobCommittedBlockCount, versionId, null); + } + + private AppendBlobItem(final String eTag, final OffsetDateTime lastModified, final byte[] contentMd5, + final boolean isServerEncrypted, final String encryptionKeySha256, final String encryptionScope, + final String blobAppendOffset, final Integer blobCommittedBlockCount, final String versionId, + final byte[] contentCrc64) { this.eTag = eTag; this.lastModified = lastModified; this.contentMd5 = CoreUtils.clone(contentMd5); @@ -85,6 +99,7 @@ public AppendBlobItem(final String eTag, final OffsetDateTime lastModified, fina this.blobAppendOffset = blobAppendOffset; this.blobCommittedBlockCount = blobCommittedBlockCount; this.versionId = versionId; + this.contentCrc64 = CoreUtils.clone(contentCrc64); } /** @@ -141,6 +156,15 @@ public byte[] getContentMd5() { return CoreUtils.clone(contentMd5); } + /** + * Gets the calculated CRC64 of the append blob. + * + * @return the calculated CRC64 of the append blob + */ + public byte[] getContentCrc64() { + return CoreUtils.clone(contentCrc64); + } + /** * Gets the offset of the append blob. * diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlockBlobItem.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlockBlobItem.java index bdad9c963208..519c5bbabf06 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlockBlobItem.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlockBlobItem.java @@ -5,6 +5,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.util.CoreUtils; +import com.azure.storage.blob.implementation.accesshelpers.BlockBlobItemConstructorProxy; import java.time.OffsetDateTime; @@ -20,6 +21,11 @@ public class BlockBlobItem { private final String encryptionKeySha256; private final String encryptionScope; private final String versionId; + private final byte[] contentCrc64; + + static { + BlockBlobItemConstructorProxy.setAccessor(BlockBlobItem::new); + } /** * Constructs a {@link BlockBlobItem}. @@ -88,6 +94,12 @@ public BlockBlobItem(final String eTag, final OffsetDateTime lastModified, final public BlockBlobItem(final String eTag, final OffsetDateTime lastModified, final byte[] contentMd5, final Boolean isServerEncrypted, final String encryptionKeySha256, final String encryptionScope, final String versionId) { + this(eTag, lastModified, contentMd5, isServerEncrypted, encryptionKeySha256, encryptionScope, versionId, null); + } + + private BlockBlobItem(final String eTag, final OffsetDateTime lastModified, final byte[] contentMd5, + final Boolean isServerEncrypted, final String encryptionKeySha256, final String encryptionScope, + final String versionId, final byte[] contentCrc64) { this.eTag = eTag; this.lastModified = lastModified; this.contentMd5 = CoreUtils.clone(contentMd5); @@ -95,6 +107,7 @@ public BlockBlobItem(final String eTag, final OffsetDateTime lastModified, final this.encryptionKeySha256 = encryptionKeySha256; this.encryptionScope = encryptionScope; this.versionId = versionId; + this.contentCrc64 = CoreUtils.clone(contentCrc64); } /** @@ -151,6 +164,15 @@ public byte[] getContentMd5() { return CoreUtils.clone(contentMd5); } + /** + * Gets the calculated CRC64 of the block blob. + * + * @return the calculated CRC64 of the block blob + */ + public byte[] getContentCrc64() { + return CoreUtils.clone(contentCrc64); + } + /** * Gets the version identifier of the block blob. * diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/PageBlobItem.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/PageBlobItem.java index ef127458a57a..39783a094794 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/PageBlobItem.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/PageBlobItem.java @@ -5,6 +5,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.util.CoreUtils; +import com.azure.storage.blob.implementation.accesshelpers.PageBlobItemConstructorProxy; import java.time.OffsetDateTime; @@ -16,12 +17,17 @@ public class PageBlobItem { private final String eTag; private final OffsetDateTime lastModified; private final byte[] contentMd5; + private final byte[] contentCrc64; private final Boolean isServerEncrypted; private final String encryptionKeySha256; private final String encryptionScope; private final Long blobSequenceNumber; private final String versionId; + static { + PageBlobItemConstructorProxy.setAccessor(PageBlobItem::new); + } + /** * Constructs a {@link PageBlobItem}. * @@ -70,9 +76,17 @@ public PageBlobItem(final String eTag, final OffsetDateTime lastModified, final public PageBlobItem(final String eTag, final OffsetDateTime lastModified, final byte[] contentMd5, final Boolean isServerEncrypted, final String encryptionKeySha256, final String encryptionScope, final Long blobSequenceNumber, final String versionId) { + this(eTag, lastModified, contentMd5, isServerEncrypted, encryptionKeySha256, encryptionScope, + blobSequenceNumber, versionId, null); + } + + private PageBlobItem(final String eTag, final OffsetDateTime lastModified, final byte[] contentMd5, + final Boolean isServerEncrypted, final String encryptionKeySha256, final String encryptionScope, + final Long blobSequenceNumber, final String versionId, final byte[] contentCrc64) { this.eTag = eTag; this.lastModified = lastModified; this.contentMd5 = CoreUtils.clone(contentMd5); + this.contentCrc64 = CoreUtils.clone(contentCrc64); this.isServerEncrypted = isServerEncrypted; this.encryptionKeySha256 = encryptionKeySha256; this.encryptionScope = encryptionScope; @@ -134,6 +148,15 @@ public byte[] getContentMd5() { return CoreUtils.clone(contentMd5); } + /** + * Gets the calculated CRC64 of the page blob. + * + * @return the calculated CRC64 of the page blob + */ + public byte[] getContentCrc64() { + return CoreUtils.clone(contentCrc64); + } + /** * Gets the current sequence number of the page blob. * diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/AppendBlobAsyncClient.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/AppendBlobAsyncClient.java index 42b7b3f76f1c..6fd42fbfe7d3 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/AppendBlobAsyncClient.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/AppendBlobAsyncClient.java @@ -18,6 +18,7 @@ import com.azure.storage.blob.BlobContainerAsyncClient; import com.azure.storage.blob.BlobServiceAsyncClient; import com.azure.storage.blob.BlobServiceVersion; +import com.azure.storage.blob.implementation.accesshelpers.AppendBlobItemConstructorProxy; import com.azure.storage.blob.implementation.models.AppendBlobsAppendBlockFromUrlHeaders; import com.azure.storage.blob.implementation.models.AppendBlobsAppendBlockHeaders; import com.azure.storage.blob.implementation.models.AppendBlobsCreateHeaders; @@ -479,9 +480,10 @@ Mono> appendBlockWithResponse(Flux data, lo null, null, getCustomerProvidedKey(), encryptionScope, context) .map(rb -> { AppendBlobsAppendBlockHeaders hd = rb.getDeserializedHeaders(); - AppendBlobItem item = new AppendBlobItem(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), - hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), - hd.getXMsBlobAppendOffset(), hd.getXMsBlobCommittedBlockCount()); + AppendBlobItem item + = AppendBlobItemConstructorProxy.create(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), + hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), + hd.getXMsBlobAppendOffset(), hd.getXMsBlobCommittedBlockCount(), null, hd.getXMsContentCrc64()); return new SimpleResponse<>(rb, item); }); } @@ -627,9 +629,10 @@ Mono> appendBlockFromUrlWithResponse(AppendBlobAppendBl getCustomerProvidedKey(), encryptionScope, context) .map(rb -> { AppendBlobsAppendBlockFromUrlHeaders hd = rb.getDeserializedHeaders(); - AppendBlobItem item = new AppendBlobItem(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), - hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), - hd.getXMsBlobAppendOffset(), hd.getXMsBlobCommittedBlockCount()); + AppendBlobItem item + = AppendBlobItemConstructorProxy.create(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), + hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), + hd.getXMsBlobAppendOffset(), hd.getXMsBlobCommittedBlockCount(), null, hd.getXMsContentCrc64()); return new SimpleResponse<>(rb, item); }); } diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/BlockBlobAsyncClient.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/BlockBlobAsyncClient.java index f938e21b4793..d1d4464f74b7 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/BlockBlobAsyncClient.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/BlockBlobAsyncClient.java @@ -15,6 +15,7 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.storage.blob.BlobAsyncClient; import com.azure.storage.blob.BlobServiceVersion; +import com.azure.storage.blob.implementation.accesshelpers.BlockBlobItemConstructorProxy; import com.azure.storage.blob.implementation.models.BlockBlobsCommitBlockListHeaders; import com.azure.storage.blob.implementation.models.BlockBlobsPutBlobFromUrlHeaders; import com.azure.storage.blob.implementation.models.BlockBlobsUploadHeaders; @@ -442,9 +443,9 @@ Mono> uploadWithResponse(BlockBlobSimpleUploadOptions op null, null, options.getHeaders(), getCustomerProvidedKey(), encryptionScope, finalContext) .map(rb -> { BlockBlobsUploadHeaders hd = rb.getDeserializedHeaders(); - BlockBlobItem item = new BlockBlobItem(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), - hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), - hd.getXMsVersionId()); + BlockBlobItem item = BlockBlobItemConstructorProxy.create(hd.getETag(), hd.getLastModified(), + hd.getContentMD5(), hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), + hd.getXMsEncryptionScope(), hd.getXMsVersionId(), hd.getXMsContentCrc64()); return new SimpleResponse<>(rb, item); })); } @@ -601,9 +602,9 @@ Mono> uploadFromUrlWithResponse(BlobUploadFromUrlOptions options.getHeaders(), getCustomerProvidedKey(), encryptionScope, context) .map(rb -> { BlockBlobsPutBlobFromUrlHeaders hd = rb.getDeserializedHeaders(); - BlockBlobItem item = new BlockBlobItem(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), - hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), - hd.getXMsVersionId()); + BlockBlobItem item = BlockBlobItemConstructorProxy.create(hd.getETag(), hd.getLastModified(), + hd.getContentMD5(), hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), + hd.getXMsEncryptionScope(), hd.getXMsVersionId(), hd.getXMsContentCrc64()); return new SimpleResponse<>(rb, item); }); } @@ -1178,9 +1179,9 @@ Mono> commitBlockListWithResponse(BlockBlobCommitBlockLi getCustomerProvidedKey(), encryptionScope, context) .map(rb -> { BlockBlobsCommitBlockListHeaders hd = rb.getDeserializedHeaders(); - BlockBlobItem item = new BlockBlobItem(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), - hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), - hd.getXMsVersionId()); + BlockBlobItem item = BlockBlobItemConstructorProxy.create(hd.getETag(), hd.getLastModified(), + hd.getContentMD5(), hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), + hd.getXMsEncryptionScope(), hd.getXMsVersionId(), hd.getXMsContentCrc64()); return new SimpleResponse<>(rb, item); }); } diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/PageBlobAsyncClient.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/PageBlobAsyncClient.java index 5943047cf49b..ab82584588c9 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/PageBlobAsyncClient.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/PageBlobAsyncClient.java @@ -22,6 +22,7 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.storage.blob.BlobAsyncClient; import com.azure.storage.blob.BlobServiceVersion; +import com.azure.storage.blob.implementation.accesshelpers.PageBlobItemConstructorProxy; import com.azure.storage.blob.implementation.models.EncryptionScope; import com.azure.storage.blob.implementation.models.PageBlobsClearPagesHeaders; import com.azure.storage.blob.implementation.models.PageBlobsCreateHeaders; @@ -529,9 +530,9 @@ Mono> uploadPagesWithResponse(PageRange pageRange, Flux { PageBlobsUploadPagesHeaders hd = rb.getDeserializedHeaders(); - PageBlobItem item = new PageBlobItem(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), - hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), - hd.getXMsBlobSequenceNumber()); + PageBlobItem item = PageBlobItemConstructorProxy.create(hd.getETag(), hd.getLastModified(), + hd.getContentMD5(), hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), + hd.getXMsEncryptionScope(), hd.getXMsBlobSequenceNumber(), null, hd.getXMsContentCrc64()); return new SimpleResponse<>(rb, item); }); } @@ -720,8 +721,9 @@ sourceCpkKey, sourceCpkKeySha256, sourceCpkAlgorithm, getCustomerProvidedKey(), context) .map(rb -> { PageBlobsUploadPagesFromURLHeaders hd = rb.getDeserializedHeaders(); - PageBlobItem item = new PageBlobItem(hd.getETag(), hd.getLastModified(), hd.getContentMD5(), - hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), hd.getXMsEncryptionScope(), null); + PageBlobItem item = PageBlobItemConstructorProxy.create(hd.getETag(), hd.getLastModified(), + hd.getContentMD5(), hd.isXMsRequestServerEncrypted(), hd.getXMsEncryptionKeySha256(), + hd.getXMsEncryptionScope(), hd.getXMsBlobSequenceNumber(), null, hd.getXMsContentCrc64()); return new SimpleResponse<>(rb, item); }); } diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobApiTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobApiTests.java index 5b65926014b6..7b9925cb808c 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobApiTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobApiTests.java @@ -54,6 +54,7 @@ import java.util.stream.Stream; import static com.azure.storage.blob.specialized.AppendBlobClient.MAX_APPEND_BLOCKS; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -358,6 +359,26 @@ public void appendBlockDefaults() { assertEquals(1, bc.getProperties().getCommittedBlockCount()); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void appendBlockDefaultsWithCrc64() { + Response appendResponse = bc.appendBlockWithResponse(DATA.getDefaultInputStream(), + DATA.getDefaultDataSize(), null, null, null, null); + + ByteArrayOutputStream downloadStream = new ByteArrayOutputStream(); + bc.downloadStream(downloadStream); + TestUtils.assertArraysEqual(DATA.getDefaultBytes(), downloadStream.toByteArray()); + + validateBasicHeaders(appendResponse.getHeaders()); + assertNotNull(appendResponse.getHeaders().getValue(X_MS_CONTENT_CRC64)); + byte[] expectedContentCrc64 + = Base64.getDecoder().decode(appendResponse.getHeaders().getValue(X_MS_CONTENT_CRC64)); + TestUtils.assertArraysEqual(expectedContentCrc64, appendResponse.getValue().getContentCrc64()); + assertNotNull(appendResponse.getValue().getBlobAppendOffset()); + assertNotNull(appendResponse.getValue().getBlobCommittedBlockCount()); + assertEquals(1, bc.getProperties().getCommittedBlockCount()); + } + @Test public void appendBlockMin() { assertResponseStatusCode( @@ -575,6 +596,29 @@ public void appendBlockFromURLMD5() { } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void appendBlockFromUrlMd5Crc64() throws NoSuchAlgorithmException { + byte[] data = getRandomByteArray(1024); + byte[] expectedContentMd5 = MessageDigest.getInstance("MD5").digest(data); + bc.appendBlock(new ByteArrayInputStream(data), data.length); + + AppendBlobClient destURL = cc.getBlobClient(generateBlobName()).getAppendBlobClient(); + destURL.create(); + + String sas = bc.generateSas(new BlobServiceSasSignatureValues(testResourceNamer.now().plusDays(1), + new BlobContainerSasPermission().setReadPermission(true))); + Response response = destURL.appendBlockFromUrlWithResponse(bc.getBlobUrl() + "?" + sas, null, + expectedContentMd5, null, null, null, Context.NONE); + + assertResponseStatusCode(response, 201); + validateBasicHeaders(response.getHeaders()); + assertArrayEquals(expectedContentMd5, response.getValue().getContentMd5()); + String contentCrc64 = response.getHeaders().getValue(X_MS_CONTENT_CRC64); + assertNotNull(contentCrc64); + assertArrayEquals(Base64.getDecoder().decode(contentCrc64), response.getValue().getContentCrc64()); + } + @Test public void appendBlockFromURLMD5Fail() { byte[] data = getRandomByteArray(1024); diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobAsyncApiTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobAsyncApiTests.java index cf7a17dade24..17d4a1e074da 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobAsyncApiTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/AppendBlobAsyncApiTests.java @@ -54,6 +54,7 @@ import java.util.stream.Stream; import static com.azure.storage.blob.specialized.AppendBlobClient.MAX_APPEND_BLOCKS; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -364,6 +365,25 @@ public void appendBlockDefaults() { .verifyComplete(); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void appendBlockDefaultsWithCrc64() { + StepVerifier.create(bc.appendBlockWithResponse(DATA.getDefaultFlux(), DATA.getDefaultDataSize(), null, null)) + .assertNext(r -> { + validateBasicHeaders(r.getHeaders()); + assertNotNull(r.getHeaders().getValue(X_MS_CONTENT_CRC64)); + byte[] expectedContentCrc64 = Base64.getDecoder().decode(r.getHeaders().getValue(X_MS_CONTENT_CRC64)); + TestUtils.assertArraysEqual(expectedContentCrc64, r.getValue().getContentCrc64()); + assertNotNull(r.getValue().getBlobAppendOffset()); + assertNotNull(r.getValue().getBlobCommittedBlockCount()); + }) + .verifyComplete(); + + StepVerifier.create(FluxUtil.collectBytesInByteBufferStream(bc.downloadStream())) + .assertNext(r -> TestUtils.assertArraysEqual(DATA.getDefaultBytes(), r)) + .verifyComplete(); + } + @Test public void appendBlockMin() { assertAsyncResponseStatusCode( @@ -591,6 +611,32 @@ public void appendBlockFromURLMD5() throws NoSuchAlgorithmException { StepVerifier.create(response).expectNextCount(1).verifyComplete(); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void appendBlockFromUrlMd5Crc64() throws NoSuchAlgorithmException { + byte[] data = getRandomByteArray(1024); + byte[] expectedContentMd5 = MessageDigest.getInstance("MD5").digest(data); + + AppendBlobAsyncClient destURL = ccAsync.getBlobAsyncClient(generateBlobName()).getAppendBlobAsyncClient(); + + String sas = bc.generateSas(new BlobServiceSasSignatureValues(testResourceNamer.now().plusDays(1), + new BlobSasPermission().setTagsPermission(true).setReadPermission(true))); + + Mono> response = bc.appendBlock(Flux.just(ByteBuffer.wrap(data)), data.length) + .then(destURL.create()) + .then(destURL.appendBlockFromUrlWithResponse(bc.getBlobUrl() + "?" + sas, null, expectedContentMd5, null, + null)); + + StepVerifier.create(response).assertNext(r -> { + assertResponseStatusCode(r, 201); + validateBasicHeaders(r.getHeaders()); + assertArrayEquals(expectedContentMd5, r.getValue().getContentMd5()); + String contentCrc64 = r.getHeaders().getValue(X_MS_CONTENT_CRC64); + assertNotNull(contentCrc64); + assertArrayEquals(Base64.getDecoder().decode(contentCrc64), r.getValue().getContentCrc64()); + }).verifyComplete(); + } + @Test public void appendBlockFromURLMD5Fail() throws NoSuchAlgorithmException { byte[] data = getRandomByteArray(1024); diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobApiTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobApiTests.java index c2a903145440..718c22897a6d 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobApiTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobApiTests.java @@ -549,6 +549,26 @@ public void commitBlockList() { assertTrue(Boolean.parseBoolean(headers.getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void commitBlockListCrc64() { + String blockID = getBlockID(); + blockBlobClient.stageBlock(blockID, DATA.getDefaultInputStream(), DATA.getDefaultDataSize()); + List ids = Collections.singletonList(blockID); + + Response response + = blockBlobClient.commitBlockListWithResponse(ids, null, null, null, null, null, null); + HttpHeaders headers = response.getHeaders(); + + byte[] expectedCrc64Content = Base64.getDecoder().decode(headers.getValue(X_MS_CONTENT_CRC64)); + + assertResponseStatusCode(response, 201); + validateBasicHeaders(headers); + assertNotNull(headers.getValue(X_MS_CONTENT_CRC64)); + TestUtils.assertArraysEqual(expectedCrc64Content, response.getValue().getContentCrc64()); + assertTrue(Boolean.parseBoolean(headers.getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); + } + @Test public void commitBlockListmin() { blockBlobClient = cc.getBlobClient(generateBlobName()).getBlockBlobClient(); @@ -839,11 +859,31 @@ public void upload() { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); blockBlobClient.downloadStream(outStream); TestUtils.assertArraysEqual(outStream.toByteArray(), DATA.getDefaultText().getBytes(StandardCharsets.UTF_8)); + validateBasicHeaders(response.getHeaders()); assertNotNull(response.getHeaders().getValue(HttpHeaderName.CONTENT_MD5)); assertTrue(Boolean.parseBoolean(response.getHeaders().getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadReturnsCrc64Content() { + Response response = blockBlobClient.uploadWithResponse(DATA.getDefaultInputStream(), + DATA.getDefaultDataSize(), null, null, null, null, null, null, null); + + assertResponseStatusCode(response, 201); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + blockBlobClient.downloadStream(outStream); + TestUtils.assertArraysEqual(outStream.toByteArray(), DATA.getDefaultText().getBytes(StandardCharsets.UTF_8)); + byte[] expectedCrc64Content = Base64.getDecoder().decode(response.getHeaders().getValue(X_MS_CONTENT_CRC64)); + + validateBasicHeaders(response.getHeaders()); + assertNotNull(response.getHeaders().getValue(HttpHeaderName.CONTENT_MD5)); + assertNotNull(response.getHeaders().getValue(X_MS_CONTENT_CRC64)); + TestUtils.assertArraysEqual(expectedCrc64Content, response.getValue().getContentCrc64()); + assertTrue(Boolean.parseBoolean(response.getHeaders().getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); + } + // Override name to prevent BinaryData.toString() invocation by test framework. @Test public void uploadBinaryData() { @@ -1505,12 +1545,35 @@ public void uploadFromUrlMax() throws NoSuchAlgorithmException { assertNotNull(blockBlobItem); assertNotNull(blockBlobItem.getETag()); assertNotNull(blockBlobItem.getLastModified()); + assertNotNull(blockBlobItem.getContentMd5()); + assertNotNull(blockBlobItem.getContentCrc64()); TestUtils.assertArraysEqual(DATA.getDefaultBytes(), os.toByteArray()); assertEquals("en-GB", destinationProperties.getContentLanguage()); assertEquals("text", destinationProperties.getContentType()); assertEquals(AccessTier.COOL, destinationProperties.getAccessTier()); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadFromUrlMaxReturnsCrc64() throws NoSuchAlgorithmException { + BlobClient sourceBlob + = primaryBlobServiceClient.getBlobContainerClient(containerName).getBlobClient(generateBlobName()); + sourceBlob.upload(DATA.getDefaultInputStream(), DATA.getDefaultDataSize()); + byte[] sourceBlobMD5 = MessageDigest.getInstance("MD5").digest(DATA.getDefaultBytes()); + String sas = sourceBlob.generateSas(new BlobServiceSasSignatureValues(testResourceNamer.now().plusDays(1), + new BlobContainerSasPermission().setReadPermission(true))); + + BlobUploadFromUrlOptions options + = new BlobUploadFromUrlOptions(sourceBlob.getBlobUrl() + "?" + sas).setContentMd5(sourceBlobMD5); + Response response = blockBlobClient.uploadFromUrlWithResponse(options, null, null); + String contentCrc64 = response.getHeaders().getValue(X_MS_CONTENT_CRC64); + BlockBlobItem blockBlobItem = response.getValue(); + + assertNotNull(contentCrc64); + assertNotNull(blockBlobItem); + TestUtils.assertArraysEqual(Base64.getDecoder().decode(contentCrc64), blockBlobItem.getContentCrc64()); + } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2020-04-08") @Test public void uploadFromWithInvalidSourceMD5() throws NoSuchAlgorithmException { diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobAsyncApiTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobAsyncApiTests.java index 664fe555846d..86d1bdd42f87 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobAsyncApiTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlockBlobAsyncApiTests.java @@ -625,8 +625,28 @@ public void commitBlockList() { .then(blockBlobAsyncClient.commitBlockListWithResponse(ids, null, null, null, null))).assertNext(r -> { assertResponseStatusCode(r, 201); HttpHeaders headers = r.getHeaders(); + + validateBasicHeaders(headers); + assertNotNull(headers.getValue(X_MS_CONTENT_CRC64)); + assertTrue(Boolean.parseBoolean(headers.getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); + }).verifyComplete(); + } + + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void commitBlockListCrc64() { + String blockID = getBlockID(); + List ids = Collections.singletonList(blockID); + + StepVerifier.create(blockBlobAsyncClient.stageBlock(blockID, DATA.getDefaultFlux(), DATA.getDefaultDataSize()) + .then(blockBlobAsyncClient.commitBlockListWithResponse(ids, null, null, null, null))).assertNext(r -> { + assertResponseStatusCode(r, 201); + HttpHeaders headers = r.getHeaders(); + byte[] expectedCrc64Content = Base64.getDecoder().decode(headers.getValue(X_MS_CONTENT_CRC64)); + validateBasicHeaders(headers); assertNotNull(headers.getValue(X_MS_CONTENT_CRC64)); + TestUtils.assertArraysEqual(expectedCrc64Content, r.getValue().getContentCrc64()); assertTrue(Boolean.parseBoolean(headers.getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); }).verifyComplete(); } @@ -948,6 +968,7 @@ public void getBlockListError() { StepVerifier.create(blockBlobAsyncClient.listBlocks(BlockListType.ALL)).verifyError(BlobStorageException.class); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") @Test public void upload() { StepVerifier @@ -956,6 +977,7 @@ public void upload() { .assertNext(r -> { assertResponseStatusCode(r, 201); validateBasicHeaders(r.getHeaders()); + assertNotNull(r.getHeaders().getValue(HttpHeaderName.CONTENT_MD5)); assertTrue(Boolean.parseBoolean(r.getHeaders().getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); }) @@ -966,6 +988,29 @@ public void upload() { .verifyComplete(); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadReturnsCrc64Content() { + StepVerifier + .create(blockBlobAsyncClient.uploadWithResponse(DATA.getDefaultFlux(), DATA.getDefaultDataSize(), null, + null, null, null, null)) + .assertNext(r -> { + assertResponseStatusCode(r, 201); + validateBasicHeaders(r.getHeaders()); + byte[] expectedCrc64Content = Base64.getDecoder().decode(r.getHeaders().getValue(X_MS_CONTENT_CRC64)); + + assertNotNull(r.getHeaders().getValue(HttpHeaderName.CONTENT_MD5)); + assertNotNull(r.getHeaders().getValue(X_MS_CONTENT_CRC64)); + TestUtils.assertArraysEqual(expectedCrc64Content, r.getValue().getContentCrc64()); + assertTrue(Boolean.parseBoolean(r.getHeaders().getValue(X_MS_REQUEST_SERVER_ENCRYPTED))); + }) + .verifyComplete(); + + StepVerifier.create(FluxUtil.collectBytesInByteBufferStream(blockBlobAsyncClient.downloadStream())) + .assertNext(r -> TestUtils.assertArraysEqual(r, DATA.getDefaultBytes())) + .verifyComplete(); + } + // Override name to prevent BinaryData.toString() invocation by test framework. @Test public void uploadBinaryData() { @@ -2459,7 +2504,7 @@ public void uploadFromUrlOverwriteFailsOnExistingBlob() { }); } - @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2020-04-08") + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-04-08") @Test public void uploadFromUrlMax() throws NoSuchAlgorithmException { BlobAsyncClient sourceBlob = primaryBlobServiceAsyncClient.getBlobContainerAsyncClient(containerName) @@ -2504,6 +2549,30 @@ public void uploadFromUrlMax() throws NoSuchAlgorithmException { .verifyComplete(); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadFromUrlMaxReturnsCrc64() throws NoSuchAlgorithmException { + BlobAsyncClient sourceBlob = primaryBlobServiceAsyncClient.getBlobContainerAsyncClient(containerName) + .getBlobAsyncClient(generateBlobName()); + byte[] sourceBlobMD5 = MessageDigest.getInstance("MD5").digest(DATA.getDefaultBytes()); + String sas = sourceBlob.generateSas(new BlobServiceSasSignatureValues(testResourceNamer.now().plusDays(1), + new BlobContainerSasPermission().setReadPermission(true))); + + BlobUploadFromUrlOptions options + = new BlobUploadFromUrlOptions(sourceBlob.getBlobUrl() + "?" + sas).setContentMd5(sourceBlobMD5); + Mono> response = sourceBlob.upload(DATA.getDefaultFlux(), null) + .then(blockBlobAsyncClient.uploadFromUrlWithResponse(options)); + + StepVerifier.create(response).assertNext(r -> { + String contentCrc64 = r.getHeaders().getValue(X_MS_CONTENT_CRC64); + BlockBlobItem blockBlobItem = r.getValue(); + + assertNotNull(contentCrc64); + assertNotNull(blockBlobItem); + TestUtils.assertArraysEqual(Base64.getDecoder().decode(contentCrc64), blockBlobItem.getContentCrc64()); + }).verifyComplete(); + } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2020-04-08") @Test public void uploadFromWithInvalidSourceMD5() throws NoSuchAlgorithmException { diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobApiTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobApiTests.java index 5c2e32f9a962..ffae25810b3f 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobApiTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobApiTests.java @@ -69,6 +69,7 @@ import java.util.Map; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -379,6 +380,23 @@ public void uploadPage() { assertTrue(response.getValue().isServerEncrypted()); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadPageCrc64() { + Response response + = bc.uploadPagesWithResponse(new PageRange().setStart(0).setEnd(PageBlobClient.PAGE_BYTES - 1), + new ByteArrayInputStream(getRandomByteArray(PageBlobClient.PAGE_BYTES)), null, null, null, null); + + byte[] expectedContentCrc64 = Base64.getDecoder().decode(response.getHeaders().getValue(X_MS_CONTENT_CRC64)); + + assertResponseStatusCode(response, 201); + assertTrue(validateBasicHeaders(response.getHeaders())); + assertNotNull(response.getHeaders().getValue(X_MS_CONTENT_CRC64)); + assertArrayEquals(expectedContentCrc64, response.getValue().getContentCrc64()); + assertEquals(0, response.getValue().getBlobSequenceNumber()); + assertTrue(response.getValue().isServerEncrypted()); + } + @Test public void uploadPageMin() { assertResponseStatusCode( @@ -582,7 +600,26 @@ public void uploadPageFromURLIA() { } @Test - public void uploadPageFromURLMD5() { + public void uploadPageFromURLMD5() throws NoSuchAlgorithmException { + PageBlobClient destURL = cc.getBlobClient(generateBlobName()).getPageBlobClient(); + destURL.create(PageBlobClient.PAGE_BYTES); + byte[] data = getRandomByteArray(PageBlobClient.PAGE_BYTES); + PageRange pageRange = new PageRange().setStart(0).setEnd(PageBlobClient.PAGE_BYTES - 1); + bc.uploadPages(pageRange, new ByteArrayInputStream(data)); + + String sas = bc.generateSas(new BlobServiceSasSignatureValues(testResourceNamer.now().plusDays(1), + new BlobContainerSasPermission().setReadPermission(true))); + Response response = destURL.uploadPagesFromUrlWithResponse(pageRange, bc.getBlobUrl() + "?" + sas, + null, MessageDigest.getInstance("MD5").digest(data), null, null, null, null); + + assertResponseStatusCode(response, 201); + assertTrue(validateBasicHeaders(response.getHeaders())); + assertNotNull(response.getHeaders().getValue(X_MS_CONTENT_CRC64)); + } + + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadPageFromURLMD5AndCrc64() throws NoSuchAlgorithmException { PageBlobClient destURL = cc.getBlobClient(generateBlobName()).getPageBlobClient(); destURL.create(PageBlobClient.PAGE_BYTES); byte[] data = getRandomByteArray(PageBlobClient.PAGE_BYTES); @@ -591,8 +628,14 @@ public void uploadPageFromURLMD5() { String sas = bc.generateSas(new BlobServiceSasSignatureValues(testResourceNamer.now().plusDays(1), new BlobContainerSasPermission().setReadPermission(true))); - assertDoesNotThrow(() -> destURL.uploadPagesFromUrlWithResponse(pageRange, bc.getBlobUrl() + "?" + sas, null, - MessageDigest.getInstance("MD5").digest(data), null, null, null, null)); + Response response = destURL.uploadPagesFromUrlWithResponse(pageRange, bc.getBlobUrl() + "?" + sas, + null, MessageDigest.getInstance("MD5").digest(data), null, null, null, null); + byte[] expectedCrc64Content = Base64.getDecoder().decode(response.getHeaders().getValue(X_MS_CONTENT_CRC64)); + + assertResponseStatusCode(response, 201); + assertTrue(validateBasicHeaders(response.getHeaders())); + assertNotNull(response.getHeaders().getValue(X_MS_CONTENT_CRC64)); + assertArrayEquals(expectedCrc64Content, response.getValue().getContentCrc64()); } @Test diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobAsyncApiTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobAsyncApiTests.java index 00f44de6b6d2..27839e45ee25 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobAsyncApiTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/PageBlobAsyncApiTests.java @@ -72,6 +72,7 @@ import java.util.Map; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -387,6 +388,25 @@ public void uploadPage() { .verifyComplete(); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadPageCrc64() { + StepVerifier + .create(bc.uploadPagesWithResponse(new PageRange().setStart(0).setEnd(PageBlobClient.PAGE_BYTES - 1), + Flux.just(ByteBuffer.wrap(getRandomByteArray(PageBlobClient.PAGE_BYTES))), null, null)) + .assertNext(r -> { + byte[] expectedContentCrc64 = Base64.getDecoder().decode(r.getHeaders().getValue(X_MS_CONTENT_CRC64)); + + assertResponseStatusCode(r, 201); + assertTrue(validateBasicHeaders(r.getHeaders())); + assertNotNull(r.getHeaders().getValue(X_MS_CONTENT_CRC64)); + assertArrayEquals(expectedContentCrc64, r.getValue().getContentCrc64()); + assertEquals(0, r.getValue().getBlobSequenceNumber()); + assertTrue(r.getValue().isServerEncrypted()); + }) + .verifyComplete(); + } + @Test public void uploadPageMin() { assertAsyncResponseStatusCode( @@ -626,6 +646,28 @@ public void uploadPageFromURLMD5() throws NoSuchAlgorithmException { StepVerifier.create(response).expectNextCount(1).verifyComplete(); } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-10-06") + @Test + public void uploadPageFromURLMD5AndCrc64() throws NoSuchAlgorithmException { + PageBlobAsyncClient destURL = ccAsync.getBlobAsyncClient(generateBlobName()).getPageBlobAsyncClient(); + byte[] data = getRandomByteArray(PageBlobClient.PAGE_BYTES); + PageRange pageRange = new PageRange().setStart(0).setEnd(PageBlobClient.PAGE_BYTES - 1); + String sas = bc.generateSas(new BlobServiceSasSignatureValues(testResourceNamer.now().plusDays(1), + new BlobSasPermission().setTagsPermission(true).setReadPermission(true))); + + Mono> response = destURL.create(PageBlobClient.PAGE_BYTES) + .then(bc.uploadPages(pageRange, Flux.just(ByteBuffer.wrap(data)))) + .then(destURL.uploadPagesFromUrlWithResponse(pageRange, bc.getBlobUrl() + "?" + sas, null, + MessageDigest.getInstance("MD5").digest(data), null, null)); + + StepVerifier.create(response).assertNext(r -> { + assertResponseStatusCode(r, 201); + assertTrue(validateBasicHeaders(r.getHeaders())); + assertArrayEquals(Base64.getDecoder().decode(r.getHeaders().getValue(X_MS_CONTENT_CRC64)), + r.getValue().getContentCrc64()); + }).verifyComplete(); + } + @Test public void uploadPageFromURLMD5Fail() throws NoSuchAlgorithmException { PageBlobAsyncClient destURL = ccAsync.getBlobAsyncClient(generateBlobName()).getPageBlobAsyncClient(); diff --git a/sdk/storage/azure-storage-blob/swagger/README.md b/sdk/storage/azure-storage-blob/swagger/README.md index 292d2f7c231d..c70c062f70e5 100644 --- a/sdk/storage/azure-storage-blob/swagger/README.md +++ b/sdk/storage/azure-storage-blob/swagger/README.md @@ -16,7 +16,7 @@ autorest ### Code generation settings ``` yaml use: '@autorest/java@4.1.63' -input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/15d7f54a5389d5906ffb4e56bb2f38fe5525c0d3/specification/storage/data-plane/Microsoft.BlobStorage/stable/2026-06-06/blob.json +input-file: https://raw.githubusercontent.com/seanmcc-msft/azure-rest-api-specs/131aabf0b2994a059097aaba48c7c034a6e98054/specification/storage/data-plane/Microsoft.BlobStorage/stable/2026-10-06/blob.json java: true output-folder: ../ namespace: com.azure.storage.blob