2 /* -----------------------------------------------------------------------
3 * <copyright file="Signature.cs" company="GRNet">
5 * Copyright 2011-2012 GRNET S.A. All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * 1. Redistributions of source code must retain the above
12 * copyright notice, this list of conditions and the following
15 * 2. Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials
18 * provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
22 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
34 * The views and conclusions contained in the software and
35 * documentation are those of the authors and should not be
36 * interpreted as representing official policies, either expressed
37 * or implied, of GRNET S.A.
39 * -----------------------------------------------------------------------
43 using System.Collections.Concurrent;
44 using System.Collections.Generic;
45 using System.Diagnostics.Contracts;
47 using System.Reflection;
48 using System.Runtime.Remoting.Metadata.W3cXsd2001;
49 using System.Security.Cryptography;
50 using System.Threading.Tasks;
53 namespace Pithos.Network
55 public static class Signature
57 private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
59 public static string CalculateMD5(FileInfo info)
62 throw new ArgumentNullException("info");
63 if (String.IsNullOrWhiteSpace(info.FullName))
64 throw new ArgumentException("info.FullName is empty","info");
65 Contract.EndContractBlock();
67 return CalculateMD5(info.FullName);
70 public static string CalculateMD5(string path)
72 if (String.IsNullOrWhiteSpace(path))
73 throw new ArgumentNullException("path");
74 Contract.EndContractBlock();
76 //DON'T calculate hashes for folders
77 if (Directory.Exists(path))
81 using (var hasher = MD5.Create())
82 using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
84 var hashBytes = hasher.ComputeHash(stream);
85 hash = hashBytes.ToHashString();
91 public static string BytesToString(byte[] hashBytes)
93 var shb=new SoapHexBinary(hashBytes);
94 return shb.ToString();
99 public static byte[] StringToBytes(string hash)
101 var shb=SoapHexBinary.Parse(hash);
106 public static byte[] ToBytes(this string hash)
108 var shb = SoapHexBinary.Parse(hash);
112 public static string ToHashString(this byte[] hashBytes)
114 var shb = new SoapHexBinary(hashBytes);
115 return shb.ToString().ToLower();
118 public static TreeHash CalculateTreeHash(FileInfo fileInfo, int blockSize, string algorithm)
121 throw new ArgumentNullException("fileInfo");
122 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
123 throw new ArgumentException("fileInfo.FullName is empty","fileInfo");
125 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
126 if (String.IsNullOrWhiteSpace(algorithm))
127 throw new ArgumentNullException("algorithm");
128 Contract.EndContractBlock();
130 return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
134 /// Calculates a file's tree hash synchronously, using the specified block size
136 /// <param name="filePath">Path to an existing file</param>
137 /// <param name="blockSize">Block size used to calculate leaf hashes</param>
138 /// <param name="algorithm"></param>
139 /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
140 public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm)
142 if (String.IsNullOrWhiteSpace(filePath))
143 throw new ArgumentNullException("filePath");
145 throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
146 if (String.IsNullOrWhiteSpace(algorithm))
147 throw new ArgumentNullException("algorithm");
148 Contract.EndContractBlock();
150 var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 2);
154 public static async Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism)
156 if (fileInfo == null)
157 throw new ArgumentNullException("fileInfo");
158 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
159 throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
161 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
162 if (String.IsNullOrWhiteSpace(algorithm))
163 throw new ArgumentNullException("algorithm");
164 Contract.EndContractBlock();
166 return await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism);
170 public static async Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism)
172 if (String.IsNullOrWhiteSpace(filePath))
173 throw new ArgumentNullException("filePath");
175 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
176 if (String.IsNullOrWhiteSpace(algorithm))
177 throw new ArgumentNullException("algorithm");
178 Contract.EndContractBlock();
180 //DON'T calculate hashes for folders
181 if (Directory.Exists(filePath))
182 return new TreeHash(algorithm);
183 //The hash of a non-existent file is the empty hash
184 if (!File.Exists(filePath))
185 return new TreeHash(algorithm);
187 //Calculate the hash of all blocks using a blockhash iterator
188 using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
190 //Calculate the blocks asyncrhonously
191 var hashes = await BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism);
193 //And then proceed with creating and returning a TreeHash
194 var length = stream.Length;
195 var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
197 var treeHash = new TreeHash(algorithm)
200 BlockSize = blockSize,
209 public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
212 throw new ArgumentNullException("hashMap");
213 if (String.IsNullOrWhiteSpace(algorithm))
214 throw new ArgumentNullException("algorithm");
215 Contract.EndContractBlock();
217 var hashCount = hashMap.Count;
218 //The tophash of an empty hashmap is an empty array
221 //The tophash of a one-item hashmap is the hash itself
225 //Calculate the required number of leaf nodes
226 var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
227 //The size of all nodes is the same and equal to the size of the input hashes
228 var hashSize = hashMap[0].Length;
230 //If the hashmap containes fewer nodes than the required leaf count, we need to fill
231 //the rest with empty blocks
233 if (hashCount < leafs)
234 empty = new byte[hashSize];
236 //New hashes will be stored in a dictionary keyed by their step to preserve order
237 var newHashes=new ConcurrentDictionary<int, byte[]>();
239 Parallel.For(0, leafs/2,
242 using (var hasher = HashAlgorithm.Create(algorithm))
245 var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
246 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
248 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
249 hasher.TransformFinalBlock(block2, 0, block2.Length);
251 var finalHash = hasher.Hash;
252 //Store the final value in its proper place
253 newHashes[step] = finalHash;
257 //Extract the hashes to a list ordered by their step
258 var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
259 return CalculateTopHash(hashes, algorithm);
263 public static byte[] CalculateHash(byte[] buffer,string algorithm)
266 throw new ArgumentNullException("buffer");
267 if (String.IsNullOrWhiteSpace(algorithm))
268 throw new ArgumentNullException("algorithm");
269 Contract.EndContractBlock();
271 using (var hasher = HashAlgorithm.Create(algorithm))
273 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);