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, 65536, 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(FileSystemInfo fileInfo, int blockSize, string algorithm)
120 if (fileInfo == null)
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 if (fileInfo is DirectoryInfo || !fileInfo.Exists)
131 return TreeHash.Empty;
133 return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
137 /// Calculates a file's tree hash synchronously, using the specified block size
139 /// <param name="filePath">Path to an existing file</param>
140 /// <param name="blockSize">Block size used to calculate leaf hashes</param>
141 /// <param name="algorithm"></param>
142 /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
143 public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm)
145 if (String.IsNullOrWhiteSpace(filePath))
146 throw new ArgumentNullException("filePath");
148 throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
149 if (String.IsNullOrWhiteSpace(algorithm))
150 throw new ArgumentNullException("algorithm");
151 Contract.EndContractBlock();
153 var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 2);
157 public static async Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism)
159 if (fileInfo == null)
160 throw new ArgumentNullException("fileInfo");
161 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
162 throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
164 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
165 if (String.IsNullOrWhiteSpace(algorithm))
166 throw new ArgumentNullException("algorithm");
167 Contract.EndContractBlock();
169 return await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism);
173 public static async Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism)
175 if (String.IsNullOrWhiteSpace(filePath))
176 throw new ArgumentNullException("filePath");
178 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
179 if (String.IsNullOrWhiteSpace(algorithm))
180 throw new ArgumentNullException("algorithm");
181 Contract.EndContractBlock();
183 if (Log.IsDebugEnabled)
184 Log.DebugFormat("Calc Signature [{0}]",filePath);
186 //DON'T calculate hashes for folders
187 if (Directory.Exists(filePath))
188 return new TreeHash(algorithm);
189 //The hash of a non-existent file is the empty hash
190 if (!File.Exists(filePath))
191 return new TreeHash(algorithm);
193 //Calculate the hash of all blocks using a blockhash iterator
194 using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
196 //Calculate the blocks asyncrhonously
197 var hashes = await BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism);
199 //And then proceed with creating and returning a TreeHash
200 var length = stream.Length;
201 var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
203 var treeHash = new TreeHash(algorithm)
206 BlockSize = blockSize,
211 var hasher = HashAlgorithm.Create("MD5");
213 treeHash.MD5= hasher.ComputeHash(stream).ToHashString();
220 public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
223 throw new ArgumentNullException("hashMap");
224 if (String.IsNullOrWhiteSpace(algorithm))
225 throw new ArgumentNullException("algorithm");
226 Contract.EndContractBlock();
228 var hashCount = hashMap.Count;
229 //The tophash of an empty hashmap is an empty array
232 //The tophash of a one-item hashmap is the hash itself
236 //Calculate the required number of leaf nodes
237 var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
238 //The size of all nodes is the same and equal to the size of the input hashes
239 var hashSize = hashMap[0].Length;
241 //If the hashmap containes fewer nodes than the required leaf count, we need to fill
242 //the rest with empty blocks
244 if (hashCount < leafs)
245 empty = new byte[hashSize];
247 //New hashes will be stored in a dictionary keyed by their step to preserve order
248 var newHashes=new ConcurrentDictionary<int, byte[]>();
250 Parallel.For(0, leafs/2,
253 using (var hasher = HashAlgorithm.Create(algorithm))
256 var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
257 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
259 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
260 hasher.TransformFinalBlock(block2, 0, block2.Length);
262 var finalHash = hasher.Hash;
263 //Store the final value in its proper place
264 newHashes[step] = finalHash;
268 //Extract the hashes to a list ordered by their step
269 var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
270 return CalculateTopHash(hashes, algorithm);
274 public static byte[] CalculateHash(byte[] buffer,string algorithm)
277 throw new ArgumentNullException("buffer");
278 if (String.IsNullOrWhiteSpace(algorithm))
279 throw new ArgumentNullException("algorithm");
280 Contract.EndContractBlock();
282 using (var hasher = HashAlgorithm.Create(algorithm))
284 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);