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 const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";
62 public static string CalculateMD5(FileSystemInfo info)
65 throw new ArgumentNullException("info");
66 if (String.IsNullOrWhiteSpace(info.FullName))
67 throw new ArgumentException("info.FullName is empty","info");
68 Contract.EndContractBlock();
70 if (info is DirectoryInfo)
73 return CalculateMD5(info.FullName);
76 public static string CalculateMD5(string path)
78 if (String.IsNullOrWhiteSpace(path))
79 throw new ArgumentNullException("path");
80 Contract.EndContractBlock();
82 //DON'T calculate hashes for folders
83 if (Directory.Exists(path))
87 using (var hasher = MD5.Create())
88 using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536, true))
90 var hashBytes = hasher.ComputeHash(stream);
91 hash = hashBytes.ToHashString();
97 public static string BytesToString(byte[] hashBytes)
99 var shb=new SoapHexBinary(hashBytes);
100 return shb.ToString();
105 public static byte[] StringToBytes(string hash)
107 var shb=SoapHexBinary.Parse(hash);
112 public static byte[] ToBytes(this string hash)
114 var shb = SoapHexBinary.Parse(hash);
118 public static string ToHashString(this byte[] hashBytes)
120 var shb = new SoapHexBinary(hashBytes);
121 return shb.ToString().ToLower();
124 public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm,IProgress<double> progress )
126 if (fileInfo == null)
127 throw new ArgumentNullException("fileInfo");
128 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
129 throw new ArgumentException("fileInfo.FullName is empty", "fileInfo");
131 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
132 if (String.IsNullOrWhiteSpace(algorithm))
133 throw new ArgumentNullException("algorithm");
134 Contract.EndContractBlock();
136 if (fileInfo is DirectoryInfo || !fileInfo.Exists)
137 return TreeHash.Empty;
139 return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm,progress);
143 /// Calculates a file's tree hash synchronously, using the specified block size
145 /// <param name="filePath">Path to an existing file</param>
146 /// <param name="blockSize">Block size used to calculate leaf hashes</param>
147 /// <param name="algorithm"></param>
148 /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
149 public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm,IProgress<double> progress )
151 if (String.IsNullOrWhiteSpace(filePath))
152 throw new ArgumentNullException("filePath");
154 throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
155 if (String.IsNullOrWhiteSpace(algorithm))
156 throw new ArgumentNullException("algorithm");
157 Contract.EndContractBlock();
158 var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 1,progress);
162 public static TreeHash CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism,IProgress<double> progress )
164 if (fileInfo == null)
165 throw new ArgumentNullException("fileInfo");
166 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
167 throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
169 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
170 if (String.IsNullOrWhiteSpace(algorithm))
171 throw new ArgumentNullException("algorithm");
172 Contract.EndContractBlock();
174 return CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism,progress);
178 public static TreeHash CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism,IProgress<double> progress )
180 if (String.IsNullOrWhiteSpace(filePath))
181 throw new ArgumentNullException("filePath");
183 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
184 if (String.IsNullOrWhiteSpace(algorithm))
185 throw new ArgumentNullException("algorithm");
186 Contract.EndContractBlock();
188 if (Log.IsDebugEnabled)
189 Log.DebugFormat("Calc Signature [{0}]",filePath);
191 //DON'T calculate hashes for folders
192 if (Directory.Exists(filePath))
193 return new TreeHash(algorithm);
194 //The hash of a non-existent file is the empty hash
195 if (!File.Exists(filePath))
196 return new TreeHash(algorithm);
198 //Calculate the hash of all blocks using a blockhash iterator
199 using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
201 //Calculate the blocks asyncrhonously
202 var hashes = BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism,progress).Result;
204 //And then proceed with creating and returning a TreeHash
205 var length = stream.Length;
206 var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
208 var treeHash = new TreeHash(algorithm)
211 BlockSize = blockSize,
216 var hasher = HashAlgorithm.Create("MD5");
218 treeHash.MD5= hasher.ComputeHash(stream).ToHashString();
225 public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
228 throw new ArgumentNullException("hashMap");
229 if (String.IsNullOrWhiteSpace(algorithm))
230 throw new ArgumentNullException("algorithm");
231 Contract.EndContractBlock();
233 var hashCount = hashMap.Count;
234 //The tophash of an empty hashmap is an empty array
237 //The tophash of a one-item hashmap is the hash itself
241 //Calculate the required number of leaf nodes
242 var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
243 //The size of all nodes is the same and equal to the size of the input hashes
244 var hashSize = hashMap[0].Length;
246 //If the hashmap containes fewer nodes than the required leaf count, we need to fill
247 //the rest with empty blocks
249 if (hashCount < leafs)
250 empty = new byte[hashSize];
252 //New hashes will be stored in a dictionary keyed by their step to preserve order
253 var newHashes=new ConcurrentDictionary<int, byte[]>();
255 Parallel.For(0, leafs/2,
258 using (var hasher = HashAlgorithm.Create(algorithm))
261 var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
262 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
264 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
265 hasher.TransformFinalBlock(block2, 0, block2.Length);
267 var finalHash = hasher.Hash;
268 //Store the final value in its proper place
269 newHashes[step] = finalHash;
273 //Extract the hashes to a list ordered by their step
274 var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
275 return CalculateTopHash(hashes, algorithm);
279 public static byte[] CalculateHash(byte[] buffer,string algorithm)
282 throw new ArgumentNullException("buffer");
283 if (String.IsNullOrWhiteSpace(algorithm))
284 throw new ArgumentNullException("algorithm");
285 Contract.EndContractBlock();
287 using (var hasher = HashAlgorithm.Create(algorithm))
289 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);