CompletableFuture<Void> downloadBlobAsync(CloudBlobMetadata cloudBlobMetadata, BlobId blobId,
      OutputStream outputStream) {
    // TODO: for GET ops, avoid extra trip to fetch metadata unless config flag is set
    // TODO: if needed, fetch metadata here and check encryption
    if (cloudBlobMetadata.getEncryptionOrigin() == EncryptionOrigin.VCR) {
      ByteBuffer encryptedBlob = ByteBuffer.allocate((int) cloudBlobMetadata.getEncryptedSize());
      return cloudDestination.downloadBlobAsync(blobId, new ByteBufferOutputStream(encryptedBlob))
          .handleAsync((unused, throwable) -> {
            // Since we may have to decrypt the blob, use handleAsync method to avoid blocking the response thread.
            if (throwable != null) {
              unwrapCompletionExceptionAndThrowStoreException(throwable, CloudStorageException.class,
                  cse -> new StoreException("Error occurred in downloading blob for blobId :" + blobId, cse,
                      StoreErrorCodes.IOError));
            }
            // Download is successful. Decrypt the blob
            ByteBuffer decryptedBlob;
            try {
              decryptedBlob = cryptoAgent.decrypt(encryptedBlob);
              outputStream.write(decryptedBlob.array());
            } catch (GeneralSecurityException | IOException e) {
              throw new CompletionException(
                  new StoreException("Error occurred in downloading blob for blobId :" + blobId,
                      StoreErrorCodes.IOError));
            }
            return unused;
          });
    } else {
      return cloudDestination.downloadBlobAsync(blobId, outputStream).exceptionally(throwable -> {
        unwrapCompletionExceptionAndThrowStoreException(throwable, CloudStorageException.class,
            cse -> new StoreException("Error occurred in downloading blob for blobId :" + blobId, cse,
                StoreErrorCodes.IOError));
        return null;
      });
    }
  }
Download the blob corresponding to the {@code blobId} from the {@code CloudDestination} to the given
{@code outputStream} asynchronously. If the blob was encrypted by vcr during upload, then this method also decrypts
it.
@param cloudBlobMetadata blob metadata to determine if the blob was encrypted by vcr during upload.
@param blobId Id of the blob to the downloaded.
@param outputStream {@code OutputStream} of the downloaded blob.
@return a {@link CompletableFuture} that will eventually complete when the blob is downloaded to the given
        {@code outputStream} or will contain the {@link StoreException} if an error occurred
ambry-cloud/src/main/java/com/github/ambry/cloud/CloudBlobStore.java