Skip to content

Only calculate full body checksum for checksums that support it#3835

Merged
sbiscigl merged 1 commit into
mainfrom
fix-tranfser
May 28, 2026
Merged

Only calculate full body checksum for checksums that support it#3835
sbiscigl merged 1 commit into
mainfrom
fix-tranfser

Conversation

@sbiscigl
Copy link
Copy Markdown
Collaborator

Description of changes:

A bug introduced in #3625 breaks MPU for object uploaded using non-default CRC64, specifically CRC32 or CRC32C. specifically an example

#include <aws/core/Aws.h>
#include <aws/core/utils/threading/Executor.h>
#include <aws/s3/S3Client.h>
#include <aws/transfer/TransferManager.h>

#include <iostream>
#include <thread>

using namespace Aws;
using namespace Aws::S3;
using namespace Aws::Transfer;

namespace {
class sdk_guard {
 public:
  sdk_guard(SDKOptions&& options) : options_(std::move(options)) { Aws::InitAPI(options_); }
  ~sdk_guard() { Aws::ShutdownAPI(options_); }

 private:
  SDKOptions options_;
};
}  // namespace

int main(int argc, char** argv) {
  if (argc < 4) {
    std::cerr << "Usage: " << argv[0] << " <bucket> <key> <file-path>" << std::endl;
    return 1;
  }
  const Aws::String bucket = argv[1];
  const Aws::String key = argv[2];
  const Aws::String filePath = argv[3];

  SDKOptions options{};
  options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
  sdk_guard guard(std::move(options));

  const unsigned threads = std::max(1u, std::thread::hardware_concurrency());
  auto executor = Aws::MakeShared<Aws::Utils::Threading::PooledThreadExecutor>("executor", threads);
  auto s3Client = Aws::MakeShared<S3Client>("s3Client");

  TransferManagerConfiguration transferConfig(executor.get());
  transferConfig.s3Client = s3Client;
  transferConfig.bufferSize = 16 * 1024 * 1024;
  transferConfig.transferBufferMaxHeapSize = threads * transferConfig.bufferSize;
  transferConfig.checksumAlgorithm = Model::ChecksumAlgorithm::CRC32;

  auto transferManager = TransferManager::Create(transferConfig);

  auto handle = transferManager->UploadFile(filePath, bucket, key, "application/octet-stream", {});
  handle->WaitUntilFinished();

  if (handle->GetStatus() != TransferStatus::COMPLETED) {
    std::cerr << "Upload failed: " << handle->GetLastError().GetMessage() << std::endl;
    return 1;
  }

  std::cout << "Uploaded " << filePath << " to s3://" << bucket << "/" << key << std::endl;
  return 0;
}

This is because we would attempt to multipart upload with a full body checksum on checksums that do not support full body.

Check all that applies:

  • Did a review by yourself.
  • Added proper tests to cover this PR. (If tests are not applicable, explain.)
  • Checked if this PR is a breaking (APIs have been changed) change.
  • Checked if this PR will not introduce cross-platform inconsistent behavior.
  • Checked if this PR would require a ReadMe/Wiki update.

Ran a manual test:

#include <aws/core/Aws.h>
#include <aws/core/utils/threading/Executor.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/transfer/TransferManager.h>

#include <iostream>
#include <thread>

using namespace Aws;
using namespace Aws::S3;
using namespace Aws::Transfer;

namespace {
class sdk_guard {
 public:
  sdk_guard(SDKOptions&& options) : options_(std::move(options)) { Aws::InitAPI(options_); }
  ~sdk_guard() { Aws::ShutdownAPI(options_); }

 private:
  SDKOptions options_;
};
}  // namespace

int main(int argc, char** argv) {
  if (argc < 4) {
    std::cerr << "Usage: " << argv[0] << " <bucket> <key> <file-path>" << std::endl;
    return 1;
  }
  const Aws::String bucket = argv[1];
  const Aws::String key = argv[2];
  const Aws::String filePath = argv[3];

  SDKOptions options{};
  options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Debug;
  sdk_guard guard(std::move(options));

  const unsigned threads = std::max(1u, std::thread::hardware_concurrency());
  auto executor = Aws::MakeShared<Aws::Utils::Threading::PooledThreadExecutor>("executor", threads);
  auto s3Client = Aws::MakeShared<S3Client>("s3Client");

  const std::pair<Model::ChecksumAlgorithm, const char*> algos[] = {
      {Model::ChecksumAlgorithm::CRC64NVME, "CRC64"},
      {Model::ChecksumAlgorithm::CRC32, "CRC32"},
      {Model::ChecksumAlgorithm::CRC32C, "CRC32C"},
      {Model::ChecksumAlgorithm::SHA256, "SHA256"},
  };

  for (const auto& [algo, name] : algos) {
    std::cout << "=== Algorithm: " << name << " ===" << std::endl;

    TransferManagerConfiguration transferConfig(executor.get());
    transferConfig.s3Client = s3Client;
    transferConfig.bufferSize = 16 * 1024 * 1024;
    transferConfig.transferBufferMaxHeapSize = threads * transferConfig.bufferSize;
    transferConfig.checksumAlgorithm = algo;

    auto transferManager = TransferManager::Create(transferConfig);

    const Aws::String iterKey = key + "." + name;

    auto handle = transferManager->UploadFile(filePath, bucket, iterKey, "application/octet-stream", {});
    handle->WaitUntilFinished();

    if (handle->GetStatus() != TransferStatus::COMPLETED) {
      std::cerr << "[" << name << "] Upload failed: " << handle->GetLastError().GetMessage() << std::endl;
      return 1;
    }
    std::cout << "[" << name << "] Uploaded to s3://" << bucket << "/" << iterKey << std::endl;

    Model::GetObjectRequest getRequest;
    getRequest.SetBucket(bucket);
    getRequest.SetKey(iterKey);
    auto getResult = s3Client->GetObject(getRequest);
    if (!getResult.IsSuccess()) {
      std::cerr << "[" << name << "] GetObject failed: " << getResult.GetError().GetMessage() << std::endl;
      return 1;
    }
    std::cout << "[" << name << "] GetObject OK — content-length="
              << getResult.GetResult().GetContentLength()
              << " etag=" << getResult.GetResult().GetETag() << std::endl;

    const Aws::String downloadPath = filePath + "." + name + ".downloaded";
    auto downloadHandle = transferManager->DownloadFile(bucket, iterKey, downloadPath);
    downloadHandle->WaitUntilFinished();

    if (downloadHandle->GetStatus() != TransferStatus::COMPLETED) {
      std::cerr << "[" << name << "] Download failed: " << downloadHandle->GetLastError().GetMessage() << std::endl;
      return 1;
    }
    std::cout << "[" << name << "] Download OK — " << downloadHandle->GetBytesTransferred()
              << " bytes -> " << downloadPath << std::endl;
  }

  return 0;
}

to verify that the checksums were working as expected. to be pulled into a integration tests later due to urgency of fix.

Check which platforms you have built SDK on to verify the correctness of this PR.

  • Linux
  • Windows
  • Android
  • MacOS
  • IOS
  • Other Platforms

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@sbiscigl sbiscigl marked this pull request as ready for review May 28, 2026 21:07
@sbiscigl sbiscigl enabled auto-merge May 28, 2026 21:09
@sbiscigl sbiscigl added this pull request to the merge queue May 28, 2026
Merged via the queue into main with commit 88f7c73 May 28, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants