/*
* 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.
*/
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Amazon.S3.Util;
using Amazon.S3Control;
using AWSSDK_DotNet.IntegrationTests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace AWSSDK_DotNet.IntegrationTests.Tests.S3
{
///
/// Integration tests for putting flexible checksums to S3
///
[TestClass]
public class ChecksumTests : TestBase
{
private static string _bucketName;
private static string _mrapArn;
private static string _testContent = "Hello world";
private const long MegSize = 1048576;
private static IEnumerable GetAlgorithmsToTest =>
new List {
new object[] { CoreChecksumAlgorithm.CRC32C },
new object[] { CoreChecksumAlgorithm.CRC32 },
new object[] { CoreChecksumAlgorithm.SHA1 },
new object[] { CoreChecksumAlgorithm.SHA256 }
};
[ClassInitialize]
public static void Setup(TestContext context)
{
_bucketName = S3TestUtils.CreateBucketWithWait(Client);
_mrapArn = S3TestUtils.GetOrCreateTestMRAP(new AmazonS3ControlClient(RegionEndpoint.USWest2), Client);
}
[ClassCleanup]
public static void Cleanup()
{
// Delete the objects in the MRAP bucket, but leave the
// MRAP and bucket for future test runs
S3TestUtils.DeleteObjects(Client, _mrapArn);
// Delete the entire bucket used for the SigV4 tests
AmazonS3Util.DeleteS3BucketWithObjects(Client, _bucketName);
}
///
/// Tests a SigV4 PutObject with the checksum placed in the header
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4SignedHeadersPut(CoreChecksumAlgorithm algorithm)
{
var putRequest = new PutObjectRequest()
{
BucketName = _bucketName,
Key = $"sigv4-headers-{algorithm}",
ContentBody = _testContent,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
UseChunkEncoding = false
};
PutAndGetChecksumTestHelper(algorithm, putRequest);
}
///
/// Tests a SigV4 PutObject with the checksum placed in the trailer
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4SignedTrailersPut(CoreChecksumAlgorithm algorithm)
{
var putRequest = new PutObjectRequest()
{
BucketName = _bucketName,
Key = $"sigv4-trailers-{algorithm}",
ContentBody = _testContent,
DisablePayloadSigning = false,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
UseChunkEncoding = true
};
PutAndGetChecksumTestHelper(algorithm, putRequest);
}
///
/// Tests a SigV4 PutObject with an unsigned payload and the checksum in the trailer
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4UnsignedTrailersPut(CoreChecksumAlgorithm algorithm)
{
var putRequest = new PutObjectRequest()
{
BucketName = _bucketName,
Key = $"sigv4-unsignedcontent-trailers-{algorithm}",
ContentBody = _testContent,
DisablePayloadSigning = true,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
UseChunkEncoding = true
};
PutAndGetChecksumTestHelper(algorithm, putRequest);
}
///
/// Tests a SigV4a PutObject with the checksum placed in the header
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4aSignedHeadersPut(CoreChecksumAlgorithm algorithm)
{
var putRequest = new PutObjectRequest()
{
BucketName = _mrapArn,
Key = $"sigv4a-headers-{algorithm}",
ContentBody = _testContent,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
UseChunkEncoding = false
};
PutAndGetChecksumTestHelper(algorithm, putRequest);
}
///
/// Tests a SigV4a PutObject with the checksum placed in the trailer
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4aSignedTrailersPut(CoreChecksumAlgorithm algorithm)
{
var putRequest = new PutObjectRequest()
{
BucketName = _mrapArn,
Key = $"sigv4a-trailers-{algorithm}",
ContentBody = _testContent,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
UseChunkEncoding = true
};
PutAndGetChecksumTestHelper(algorithm, putRequest);
}
///
/// Tests a SigV4a PutObject with an unsigned payload and the checksum in the trailer
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4aUnsignedTrailersPut(CoreChecksumAlgorithm algorithm)
{
var putRequest = new PutObjectRequest()
{
BucketName = _mrapArn,
Key = $"sigv4a-unsignedcontent-trailers-{algorithm}",
ContentBody = _testContent,
DisablePayloadSigning = true,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
UseChunkEncoding = true
};
PutAndGetChecksumTestHelper(algorithm, putRequest);
}
///
/// Puts and gets an object using a flexible checksum
///
/// Checksum algorithm to use
/// PutObject request
private void PutAndGetChecksumTestHelper(CoreChecksumAlgorithm algorithm, PutObjectRequest putRequest)
{
Client.PutObject(putRequest);
var getObjectAttributesRequest = new GetObjectAttributesRequest()
{
BucketName = putRequest.BucketName,
Key = putRequest.Key,
ObjectAttributes = new List { ObjectAttributes.Checksum }
};
var getObjectAttributesResponse = Client.GetObjectAttributes(getObjectAttributesRequest);
Assert.IsNotNull(getObjectAttributesResponse);
var getRequest = new GetObjectRequest
{
BucketName = putRequest.BucketName,
Key = putRequest.Key,
ChecksumMode = ChecksumMode.ENABLED
};
var response = Client.GetObject(getRequest);
Assert.AreEqual(algorithm, response.ResponseMetadata.ChecksumAlgorithm);
Assert.AreEqual(ChecksumValidationStatus.PENDING_RESPONSE_READ, response.ResponseMetadata.ChecksumValidationStatus);
// Ensures checksum was calculated, an exception will have been thrown if it didn't match
new StreamReader(response.ResponseStream).ReadToEnd();
response.ResponseStream.Dispose();
}
///
/// Tests a SigV4 multipart upload with a signed body
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4SignedMultipartUpload(CoreChecksumAlgorithm algorithm)
{
MultipartTestHelper(algorithm, _bucketName, false);
}
///
/// Tests a SigV4 multipart upload with an unsigned body
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4UnsignedMultipartUpload(CoreChecksumAlgorithm algorithm)
{
MultipartTestHelper(algorithm, _bucketName, true);
}
///
/// Tests a SigV4a multipart upload with a signed body
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4aSignedMultipartUpload(CoreChecksumAlgorithm algorithm)
{
MultipartTestHelper(algorithm, _mrapArn, false);
}
///
/// Tests a SigV4a multipart upload with an unsigned body
///
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestV4aUnsignedMultipartUpload(CoreChecksumAlgorithm algorithm)
{
MultipartTestHelper(algorithm, _mrapArn, true);
}
///
/// Test helper to test a multipart upload without using the Transfer Utility
///
/// checksum algorithm
/// bucket to upload the object to
/// whether the request payload should be signed
private void MultipartTestHelper(CoreChecksumAlgorithm algorithm, string bucketName, bool disablePayloadSigning)
{
var random = new Random();
var nextRandom = random.Next();
var filePath = Path.Combine(Path.GetTempPath(), "multi-" + nextRandom + ".txt");
var retrievedFilepath = Path.Combine(Path.GetTempPath(), "retreived-" + nextRandom + ".txt");
var totalSize = MegSize * 15;
UtilityMethods.GenerateFile(filePath, totalSize);
string key = "key-" + random.Next();
Stream inputStream = File.OpenRead(filePath);
try
{
InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest()
{
BucketName = bucketName,
Key = key,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString())
};
InitiateMultipartUploadResponse initResponse = Client.InitiateMultipartUpload(initRequest);
// Upload part 1
UploadPartRequest uploadRequest = new UploadPartRequest()
{
BucketName = bucketName,
Key = key,
UploadId = initResponse.UploadId,
PartNumber = 1,
PartSize = 5 * MegSize,
InputStream = inputStream,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
DisablePayloadSigning = disablePayloadSigning
};
UploadPartResponse up1Response = Client.UploadPart(uploadRequest);
// Upload part 2
uploadRequest = new UploadPartRequest()
{
BucketName = bucketName,
Key = key,
UploadId = initResponse.UploadId,
PartNumber = 2,
PartSize = 5 * MegSize,
InputStream = inputStream,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
DisablePayloadSigning = disablePayloadSigning
};
UploadPartResponse up2Response = Client.UploadPart(uploadRequest);
// Upload part 3
uploadRequest = new UploadPartRequest()
{
BucketName = bucketName,
Key = key,
UploadId = initResponse.UploadId,
PartNumber = 3,
InputStream = inputStream,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString()),
DisablePayloadSigning = disablePayloadSigning,
IsLastPart = true
};
UploadPartResponse up3Response = Client.UploadPart(uploadRequest);
ListPartsRequest listPartRequest = new ListPartsRequest()
{
BucketName = bucketName,
Key = key,
UploadId = initResponse.UploadId
};
ListPartsResponse listPartResponse = Client.ListParts(listPartRequest);
Assert.AreEqual(3, listPartResponse.Parts.Count);
AssertPartsAreEqual(up1Response, listPartResponse.Parts[0]);
AssertPartsAreEqual(up2Response, listPartResponse.Parts[1]);
AssertPartsAreEqual(up3Response, listPartResponse.Parts[2]);
CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest()
{
BucketName = bucketName,
Key = key,
UploadId = initResponse.UploadId
};
compRequest.AddPartETags(up1Response, up2Response, up3Response);
CompleteMultipartUploadResponse compResponse = Client.CompleteMultipartUpload(compRequest);
Assert.IsNotNull(compResponse.ETag);
Assert.AreEqual(key, compResponse.Key);
Assert.IsNotNull(compResponse.Location);
// Get the file back from S3 and assert it is still the same.
var getRequest = new GetObjectRequest
{
BucketName = bucketName,
Key = key,
ChecksumMode = ChecksumMode.ENABLED
};
var getResponse = Client.GetObject(getRequest);
getResponse.WriteResponseStreamToFile(retrievedFilepath);
UtilityMethods.CompareFiles(filePath, retrievedFilepath);
// We don't expect the checksum to be validated on getting an entire multipart object,
// because it's actually the checksum-of-checksums
Assert.AreEqual(CoreChecksumAlgorithm.NONE, getResponse.ResponseMetadata.ChecksumAlgorithm);
Assert.AreEqual(ChecksumValidationStatus.NOT_VALIDATED, getResponse.ResponseMetadata.ChecksumValidationStatus);
}
finally
{
inputStream.Close();
if (File.Exists(filePath))
File.Delete(filePath);
if (File.Exists(retrievedFilepath))
File.Delete(retrievedFilepath);
}
}
///
/// Helper to assert that uploaded parts have the same checksum as listed parts.
/// Genearlly only one checksum is expected to be set.
///
/// Response after uploading a part
/// Response for a single part after listing parts
private static void AssertPartsAreEqual(UploadPartResponse uploadPartResponse, PartDetail partDetail)
{
Assert.AreEqual(uploadPartResponse.PartNumber, partDetail.PartNumber);
Assert.AreEqual(uploadPartResponse.ETag, partDetail.ETag);
Assert.AreEqual(uploadPartResponse.ChecksumCRC32C, partDetail.ChecksumCRC32C);
Assert.AreEqual(uploadPartResponse.ChecksumCRC32, partDetail.ChecksumCRC32);
Assert.AreEqual(uploadPartResponse.ChecksumSHA1, partDetail.ChecksumSHA1);
Assert.AreEqual(uploadPartResponse.ChecksumSHA256, partDetail.ChecksumSHA256);
}
[TestMethod]
[TestCategory("S3")]
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestMultipartUploadViaTransferUtility(CoreChecksumAlgorithm algorithm)
{
var transferConfig = new TransferUtilityConfig { MinSizeBeforePartUpload = 6000000 };
var transfer = new TransferUtility(Client, transferConfig);
var content = new string('a', 7000000);
var key = UtilityMethods.GenerateName(nameof(ChecksumTests));
var filePath = Path.Combine(Path.GetTempPath(), key + ".txt");
var retrievedFilepath = Path.Combine(Path.GetTempPath(), "retreived-" + key + ".txt");
try
{
// Create the file
using (StreamWriter writer = File.CreateText(filePath))
{
writer.Write(content);
}
var uploadRequest = new TransferUtilityUploadRequest
{
BucketName = _bucketName,
Key = key,
FilePath = filePath,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString())
};
transfer.Upload(uploadRequest);
// Get the file back from S3 and assert it is still the same.
GetObjectRequest getRequest = new GetObjectRequest
{
BucketName = _bucketName,
Key = uploadRequest.Key,
ChecksumMode = ChecksumMode.ENABLED
};
var getResponse = Client.GetObject(getRequest);
var getBody = new StreamReader(getResponse.ResponseStream).ReadToEnd();
Assert.AreEqual(content, getBody);
// We don't expect the checksum to be validated on getting an entire multipart object,
// because it's actually the checksum-of-checksums
Assert.AreEqual(CoreChecksumAlgorithm.NONE, getResponse.ResponseMetadata.ChecksumAlgorithm);
Assert.AreEqual(ChecksumValidationStatus.NOT_VALIDATED, getResponse.ResponseMetadata.ChecksumValidationStatus);
// Get the object attributes. Parts collection in ObjectParts is only returned if ChecksumAlgorithm is set different from default value.
GetObjectAttributesRequest getObjectAttributesRequest = new GetObjectAttributesRequest()
{
BucketName = _bucketName,
Key = uploadRequest.Key,
ObjectAttributes = new List()
{
new ObjectAttributes("Checksum"),
new ObjectAttributes("ObjectParts"),
new ObjectAttributes("ObjectSize")
}
};
GetObjectAttributesResponse getObjectAttributesResponse = Client.GetObjectAttributes(getObjectAttributesRequest);
Assert.IsTrue(getObjectAttributesResponse.ObjectParts.Parts.Count > 0);
// Number of Parts returned is controlled by GetObjectAttributesRequest.MaxParts.
Assert.AreEqual(getObjectAttributesResponse.ObjectParts.Parts.Count, getObjectAttributesResponse.ObjectParts.TotalPartsCount);
var firstObjectPart = getObjectAttributesResponse.ObjectParts.Parts.First();
ChecksumAlgorithm expectedChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString());
if (expectedChecksumAlgorithm == ChecksumAlgorithm.CRC32)
{
Assert.IsNotNull(firstObjectPart.ChecksumCRC32);
}
if (expectedChecksumAlgorithm == ChecksumAlgorithm.CRC32C)
{
Assert.IsNotNull(firstObjectPart.ChecksumCRC32C);
}
if (expectedChecksumAlgorithm == ChecksumAlgorithm.SHA1)
{
Assert.IsNotNull(firstObjectPart.ChecksumSHA1);
}
if (expectedChecksumAlgorithm == ChecksumAlgorithm.SHA256)
{
Assert.IsNotNull(firstObjectPart.ChecksumSHA256);
}
Assert.AreEqual(1, firstObjectPart.PartNumber);
Assert.IsTrue(firstObjectPart.Size > 0);
// Similarily we don't expect this to validate either,
// though it doesn't expose the reponse metadata
transfer.Download(new TransferUtilityDownloadRequest
{
BucketName = _bucketName,
Key = uploadRequest.Key,
FilePath = retrievedFilepath,
ChecksumMode = ChecksumMode.ENABLED
});
}
finally
{
if (File.Exists(filePath))
File.Delete(filePath);
if (File.Exists(retrievedFilepath))
File.Delete(retrievedFilepath);
}
}
[TestMethod]
[TestCategory("S3")]
[DataTestMethod]
[DynamicData(nameof(GetAlgorithmsToTest))]
public void TestSingleUploadViaTransferUtility(CoreChecksumAlgorithm algorithm)
{
var transferConfig = new TransferUtilityConfig { MinSizeBeforePartUpload = 6000000 };
var transfer = new TransferUtility(Client, transferConfig);
var content = new string('a', 5000000);
var key = UtilityMethods.GenerateName(nameof(ChecksumTests));
var filePath = Path.Combine(Path.GetTempPath(), key + ".txt");
var retrievedFilepath = Path.Combine(Path.GetTempPath(), "retreived-" + key + ".txt");
try
{
// Create the file
using (StreamWriter writer = File.CreateText(filePath))
{
writer.Write(content);
}
var uploadRequest = new TransferUtilityUploadRequest
{
BucketName = _bucketName,
Key = key,
FilePath = filePath,
ChecksumAlgorithm = ChecksumAlgorithm.FindValue(algorithm.ToString())
};
transfer.Upload(uploadRequest);
// Get the file back from S3 and assert it is still the same.
var getRequest = new GetObjectRequest
{
BucketName = _bucketName,
Key = uploadRequest.Key,
ChecksumMode = ChecksumMode.ENABLED
};
var getResponse = Client.GetObject(getRequest);
var getBody = new StreamReader(getResponse.ResponseStream).ReadToEnd();
Assert.AreEqual(content, getBody);
Assert.AreEqual(algorithm.ToString(), getResponse.ResponseMetadata.ChecksumAlgorithm.ToString(), true);
Assert.AreEqual(ChecksumValidationStatus.PENDING_RESPONSE_READ, getResponse.ResponseMetadata.ChecksumValidationStatus);
// This should validate the checksum, so "assert" that no exceptions are thrown,
// though it doesn't expose the response metadata like above
transfer.Download(new TransferUtilityDownloadRequest
{
BucketName = _bucketName,
Key = uploadRequest.Key,
FilePath = retrievedFilepath,
ChecksumMode = ChecksumMode.ENABLED
});
}
finally
{
if (File.Exists(filePath))
File.Delete(filePath);
if (File.Exists(retrievedFilepath))
File.Delete(retrievedFilepath);
}
}
}
}