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.Runtime.Remoting.Metadata.W3cXsd2001;
48 using System.Security.Cryptography;
49 using System.Threading.Tasks;
52 namespace Pithos.Network
54 public static class Signature
56 public static string CalculateMD5(FileInfo info)
59 throw new ArgumentNullException("info");
60 if (String.IsNullOrWhiteSpace(info.FullName))
61 throw new ArgumentException("info.FullName is empty","info");
62 Contract.EndContractBlock();
64 return CalculateMD5(info.FullName);
67 public static string CalculateMD5(string path)
69 if (String.IsNullOrWhiteSpace(path))
70 throw new ArgumentNullException("path");
71 Contract.EndContractBlock();
73 //DON'T calculate hashes for folders
74 if (Directory.Exists(path))
78 using (var hasher = MD5.Create())
79 using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
81 var hashBytes = hasher.ComputeHash(stream);
82 hash = hashBytes.ToHashString();
88 public static string BytesToString(byte[] hashBytes)
90 var shb=new SoapHexBinary(hashBytes);
91 return shb.ToString();
96 public static byte[] StringToBytes(string hash)
98 var shb=SoapHexBinary.Parse(hash);
103 public static byte[] ToBytes(this string hash)
105 var shb = SoapHexBinary.Parse(hash);
109 public static string ToHashString(this byte[] hashBytes)
111 var shb = new SoapHexBinary(hashBytes);
112 return shb.ToString().ToLower();
115 public static TreeHash CalculateTreeHash(FileInfo fileInfo, int blockSize, string algorithm)
118 throw new ArgumentNullException("fileInfo");
119 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
120 throw new ArgumentException("fileInfo.FullName is empty","fileInfo");
122 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
123 if (String.IsNullOrWhiteSpace(algorithm))
124 throw new ArgumentNullException("algorithm");
125 Contract.EndContractBlock();
127 return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
131 /// Calculates a file's tree hash synchronously, using the specified block size
133 /// <param name="filePath">Path to an existing file</param>
134 /// <param name="blockSize">Block size used to calculate leaf hashes</param>
135 /// <param name="algorithm"></param>
136 /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
137 public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm)
139 if (String.IsNullOrWhiteSpace(filePath))
140 throw new ArgumentNullException("filePath");
142 throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
143 if (String.IsNullOrWhiteSpace(algorithm))
144 throw new ArgumentNullException("algorithm");
145 Contract.EndContractBlock();
147 var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 2);
151 public static async Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism)
153 if (fileInfo == null)
154 throw new ArgumentNullException("fileInfo");
155 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
156 throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
158 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
159 if (String.IsNullOrWhiteSpace(algorithm))
160 throw new ArgumentNullException("algorithm");
161 Contract.EndContractBlock();
163 return await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism);
167 public static async Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism)
169 if (String.IsNullOrWhiteSpace(filePath))
170 throw new ArgumentNullException("filePath");
172 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
173 if (String.IsNullOrWhiteSpace(algorithm))
174 throw new ArgumentNullException("algorithm");
175 Contract.EndContractBlock();
177 //DON'T calculate hashes for folders
178 if (Directory.Exists(filePath))
179 return new TreeHash(algorithm);
180 //The hash of a non-existent file is the empty hash
181 if (!File.Exists(filePath))
182 return new TreeHash(algorithm);
184 //Calculate the hash of all blocks using a blockhash iterator
185 using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
187 //Calculate the blocks asyncrhonously
188 var hashes = await BlockHashAlgorithms.CalculateBlockHashesAgentAsync(stream, blockSize, algorithm, parallelism);
190 //And then proceed with creating and returning a TreeHash
191 var length = stream.Length;
192 var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
194 var treeHash = new TreeHash(algorithm)
197 BlockSize = blockSize,
206 public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
209 throw new ArgumentNullException("hashMap");
210 if (String.IsNullOrWhiteSpace(algorithm))
211 throw new ArgumentNullException("algorithm");
212 Contract.EndContractBlock();
214 var hashCount = hashMap.Count;
215 //The tophash of an empty hashmap is an empty array
218 //The tophash of a one-item hashmap is the hash itself
222 //Calculate the required number of leaf nodes
223 var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
224 //The size of all nodes is the same and equal to the size of the input hashes
225 var hashSize = hashMap[0].Length;
227 //If the hashmap containes fewer nodes than the required leaf count, we need to fill
228 //the rest with empty blocks
230 if (hashCount < leafs)
231 empty = new byte[hashSize];
233 //New hashes will be stored in a dictionary keyed by their step to preserve order
234 var newHashes=new ConcurrentDictionary<int, byte[]>();
236 Parallel.For(0, leafs/2,
239 using (var hasher = HashAlgorithm.Create(algorithm))
242 var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
243 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
245 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
246 hasher.TransformFinalBlock(block2, 0, block2.Length);
248 var finalHash = hasher.Hash;
249 //Store the final value in its proper place
250 newHashes[step] = finalHash;
254 //Extract the hashes to a list ordered by their step
255 var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
256 return CalculateTopHash(hashes, algorithm);
260 public static byte[] CalculateHash(byte[] buffer,string algorithm)
263 throw new ArgumentNullException("buffer");
264 if (String.IsNullOrWhiteSpace(algorithm))
265 throw new ArgumentNullException("algorithm");
266 Contract.EndContractBlock();
268 using (var hasher = HashAlgorithm.Create(algorithm))
270 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);