/*
* 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 System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Amazon.Runtime;
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
using System.Globalization;
namespace Amazon.Glacier
{
///
/// This class computes the tree hash that is set on the CheckSum property on UploadArchiveRequest, UploadMultipartPartRequest and CompleteMultipartUploadRequest.
///
public static class TreeHashGenerator
{
private const long MB = 1024 * 1024;
///
/// This method computes the tree hash used by glacier. It reads the whole stream and then resets the
/// position of the stream back to the beginning.
///
/// The stream to read and compute the tree hash for.
/// The tree hash that can be used to set the the Checksum property.
public static string CalculateTreeHash(Stream stream)
{
List hashes = computePartHashs(stream);
byte[] treeHash = computeTreehHash(hashes);
return BitConverter.ToString(treeHash).Replace("-", "").ToLowerInvariant();
}
///
/// This method computes the final tree hash used on the CompleteMultipartUploadRequest from the tree hashes of
/// the individual part uploaded.
///
/// The list of tree hashes for the individual parts.
/// The tree hash that can be used to set the the Checksum property.
public static string CalculateTreeHash(IEnumerable partsTreeHash)
{
List hashes = new List();
foreach (var partTreeHash in partsTreeHash)
{
hashes.Add(stringToByteArrayFastest(partTreeHash));
}
byte[] treeHash = computeTreehHash(hashes);
return BitConverter.ToString(treeHash).Replace("-", "").ToLowerInvariant();
}
static List computePartHashs(Stream stream)
{
long initialPosition = stream.Position;
List hashes = new List();
while (stream.Position < stream.Length)
{
Stream wrapper = new PartialWrapperStream(stream, MB);
byte[] currentHash = CryptoUtilFactory.CryptoInstance.ComputeSHA256Hash(wrapper);
hashes.Add(currentHash);
}
stream.Seek(initialPosition, SeekOrigin.Begin);
return hashes;
}
static byte[] computeTreehHash(List partHashsums)
{
/*
* The tree hash algorithm involves concatenating adjacent pairs of
* individual SHA256 sums, then taking the SHA256 sum of the resulting bytes
* and storing it, then recursing on this new list until there is only
* one element. Any final odd-numbered parts at each step are carried
* over to the next iteration as-is.
*/
while (partHashsums.Count > 1)
{
List treeHashes = new List();
for (int i = 0; i < partHashsums.Count / 2; i++)
{
byte[] firstPart = partHashsums[2 * i];
byte[] secondPart = partHashsums[2 * i + 1];
byte[] concatenation = new byte[firstPart.Length + secondPart.Length];
Array.Copy(firstPart, 0, concatenation, 0, firstPart.Length);
Array.Copy(secondPart, 0, concatenation, firstPart.Length, secondPart.Length);
treeHashes.Add(CryptoUtilFactory.CryptoInstance.ComputeSHA256Hash(concatenation));
}
if (partHashsums.Count % 2 == 1)
{
treeHashes.Add(partHashsums[partHashsums.Count - 1]);
}
partHashsums = treeHashes;
}
return partHashsums[0];
}
static byte[] stringToByteArrayFastest(string hex)
{
if (hex.Length % 2 == 1)
throw new ArgumentOutOfRangeException("hex", "The binary key cannot have an odd number of digits");
byte[] arr = new byte[hex.Length >> 1];
for (int i = 0; i < (hex.Length >> 1); ++i)
{
arr[i] = (byte)((getHexVal(hex[i << 1]) << 4) + (getHexVal(hex[(i << 1) + 1])));
}
return arr;
}
static int getHexVal(char hex)
{
int val = (int)hex;
return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
}
}
}