#region /* ----------------------------------------------------------------------- * * * Copyright 2011-2012 GRNET S.A. All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and * documentation are those of the authors and should not be * interpreted as representing official policies, either expressed * or implied, of GRNET S.A. * * ----------------------------------------------------------------------- */ #endregion using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.IO; using System.Reflection; using System.Runtime.Remoting.Metadata.W3cXsd2001; using System.Security.Cryptography; using System.Threading.Tasks; using System.Linq; namespace Pithos.Network { public static class Signature { private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); public static string CalculateMD5(FileInfo info) { if (info==null) throw new ArgumentNullException("info"); if (String.IsNullOrWhiteSpace(info.FullName)) throw new ArgumentException("info.FullName is empty","info"); Contract.EndContractBlock(); return CalculateMD5(info.FullName); } public static string CalculateMD5(string path) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); Contract.EndContractBlock(); //DON'T calculate hashes for folders if (Directory.Exists(path)) return ""; string hash; using (var hasher = MD5.Create()) using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) { var hashBytes = hasher.ComputeHash(stream); hash = hashBytes.ToHashString(); } return hash; } /* public static string BytesToString(byte[] hashBytes) { var shb=new SoapHexBinary(hashBytes); return shb.ToString(); } public static byte[] StringToBytes(string hash) { var shb=SoapHexBinary.Parse(hash); return shb.Value; } */ public static byte[] ToBytes(this string hash) { var shb = SoapHexBinary.Parse(hash); return shb.Value; } public static string ToHashString(this byte[] hashBytes) { var shb = new SoapHexBinary(hashBytes); return shb.ToString().ToLower(); } public static TreeHash CalculateTreeHash(FileInfo fileInfo, int blockSize, string algorithm) { if (fileInfo==null) throw new ArgumentNullException("fileInfo"); if (String.IsNullOrWhiteSpace(fileInfo.FullName)) throw new ArgumentException("fileInfo.FullName is empty","fileInfo"); if (blockSize <= 0) throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero "); if (String.IsNullOrWhiteSpace(algorithm)) throw new ArgumentNullException("algorithm"); Contract.EndContractBlock(); return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm); } /// /// Calculates a file's tree hash synchronously, using the specified block size /// /// Path to an existing file /// Block size used to calculate leaf hashes /// /// A with the block hashes and top hash public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm) { if (String.IsNullOrWhiteSpace(filePath)) throw new ArgumentNullException("filePath"); if (blockSize<=0) throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero "); if (String.IsNullOrWhiteSpace(algorithm)) throw new ArgumentNullException("algorithm"); Contract.EndContractBlock(); var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 2); return hash.Result; } public static async Task CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism) { if (fileInfo == null) throw new ArgumentNullException("fileInfo"); if (String.IsNullOrWhiteSpace(fileInfo.FullName)) throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo"); if (blockSize <= 0) throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero "); if (String.IsNullOrWhiteSpace(algorithm)) throw new ArgumentNullException("algorithm"); Contract.EndContractBlock(); return await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism); } public static async Task CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism) { if (String.IsNullOrWhiteSpace(filePath)) throw new ArgumentNullException("filePath"); if (blockSize <= 0) throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero "); if (String.IsNullOrWhiteSpace(algorithm)) throw new ArgumentNullException("algorithm"); Contract.EndContractBlock(); //DON'T calculate hashes for folders if (Directory.Exists(filePath)) return new TreeHash(algorithm); //The hash of a non-existent file is the empty hash if (!File.Exists(filePath)) return new TreeHash(algorithm); //Calculate the hash of all blocks using a blockhash iterator using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true)) { //Calculate the blocks asyncrhonously var hashes = await BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism); //And then proceed with creating and returning a TreeHash var length = stream.Length; var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList(); var treeHash = new TreeHash(algorithm) { Bytes = length, BlockSize = blockSize, Hashes = list }; return treeHash; } } public static byte[] CalculateTopHash(IList hashMap, string algorithm) { if (hashMap == null) throw new ArgumentNullException("hashMap"); if (String.IsNullOrWhiteSpace(algorithm)) throw new ArgumentNullException("algorithm"); Contract.EndContractBlock(); var hashCount = hashMap.Count; //The tophash of an empty hashmap is an empty array if (hashCount == 0) return new byte[0]; //The tophash of a one-item hashmap is the hash itself if (hashCount == 1) return hashMap[0]; //Calculate the required number of leaf nodes var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2))); //The size of all nodes is the same and equal to the size of the input hashes var hashSize = hashMap[0].Length; //If the hashmap containes fewer nodes than the required leaf count, we need to fill //the rest with empty blocks byte[] empty=null; if (hashCount < leafs) empty = new byte[hashSize]; //New hashes will be stored in a dictionary keyed by their step to preserve order var newHashes=new ConcurrentDictionary(); Parallel.For(0, leafs/2, (step, state) => { using (var hasher = HashAlgorithm.Create(algorithm)) { var i = step*2; var block1 = i <= hashCount - 1 ? hashMap[i] : empty; var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty; hasher.TransformBlock(block1, 0, block1.Length, null, 0); hasher.TransformFinalBlock(block2, 0, block2.Length); var finalHash = hasher.Hash; //Store the final value in its proper place newHashes[step] = finalHash; } }); //Extract the hashes to a list ordered by their step var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList(); return CalculateTopHash(hashes, algorithm); } public static byte[] CalculateHash(byte[] buffer,string algorithm) { if (buffer == null) throw new ArgumentNullException("buffer"); if (String.IsNullOrWhiteSpace(algorithm)) throw new ArgumentNullException("algorithm"); Contract.EndContractBlock(); using (var hasher = HashAlgorithm.Create(algorithm)) { var hash = hasher.ComputeHash(buffer, 0, buffer.Length); return hash; } } } }