2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Diagnostics.Contracts;
6 using System.Runtime.Remoting.Metadata.W3cXsd2001;
7 using System.Security.Cryptography;
9 using System.Threading.Tasks;
12 namespace Pithos.Network
14 public static class Signature
16 public static string CalculateMD5(FileInfo info)
19 throw new ArgumentNullException("info");
20 if (String.IsNullOrWhiteSpace(info.FullName))
21 throw new ArgumentException("info.FullName is empty","info");
22 Contract.EndContractBlock();
24 return CalculateMD5(info.FullName);
27 public static string CalculateMD5(string path)
29 if (String.IsNullOrWhiteSpace(path))
30 throw new ArgumentNullException("path");
31 Contract.EndContractBlock();
33 //DON'T calculate hashes for folders
34 if (Directory.Exists(path))
38 using (var hasher = MD5.Create())
39 using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
41 var hashBytes = hasher.ComputeHash(stream);
42 hash = hashBytes.ToHashString();
48 public static string BytesToString(byte[] hashBytes)
50 var shb=new SoapHexBinary(hashBytes);
51 return shb.ToString();
56 public static byte[] StringToBytes(string hash)
58 var shb=SoapHexBinary.Parse(hash);
63 public static byte[] ToBytes(this string hash)
65 var shb = SoapHexBinary.Parse(hash);
69 public static string ToHashString(this byte[] hashBytes)
71 var shb = new SoapHexBinary(hashBytes);
72 return shb.ToString().ToLower();
75 public static TreeHash CalculateTreeHash(FileInfo fileInfo, int blockSize, string algorithm)
78 throw new ArgumentNullException("fileInfo");
79 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
80 throw new ArgumentException("fileInfo.FullName is empty","fileInfo");
82 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
83 if (String.IsNullOrWhiteSpace(algorithm))
84 throw new ArgumentNullException("algorithm");
85 Contract.EndContractBlock();
87 return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
91 /// Calculates a file's tree hash synchronously, using the specified block size
93 /// <param name="filePath">Path to an existing file</param>
94 /// <param name="blockSize">Block size used to calculate leaf hashes</param>
95 /// <param name="algorithm"></param>
96 /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
97 public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm)
99 if (String.IsNullOrWhiteSpace(filePath))
100 throw new ArgumentNullException("filePath");
102 throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
103 if (String.IsNullOrWhiteSpace(algorithm))
104 throw new ArgumentNullException("algorithm");
105 Contract.EndContractBlock();
107 var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm);
111 public static async Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm)
113 if (fileInfo == null)
114 throw new ArgumentNullException("fileInfo");
115 if (String.IsNullOrWhiteSpace(fileInfo.FullName))
116 throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
118 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
119 if (String.IsNullOrWhiteSpace(algorithm))
120 throw new ArgumentNullException("algorithm");
121 Contract.EndContractBlock();
123 return await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm);
127 public static async Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm)
129 if (String.IsNullOrWhiteSpace(filePath))
130 throw new ArgumentNullException("filePath");
132 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
133 if (String.IsNullOrWhiteSpace(algorithm))
134 throw new ArgumentNullException("algorithm");
135 Contract.EndContractBlock();
137 //DON'T calculate hashes for folders
138 if (Directory.Exists(filePath))
139 return new TreeHash(algorithm);
140 //The hash of a non-existent file is the empty hash
141 if (!File.Exists(filePath))
142 return new TreeHash(algorithm);
144 //Calculate the hash of all blocks using a blockhash iterator
145 using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
147 //Calculate the blocks asyncrhonously
148 var hashes = await CalculateBlockHashesAsync(stream, blockSize, algorithm);
150 //And then proceed with creating and returning a TreeHash
151 var length = stream.Length;
152 var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
154 var treeHash = new TreeHash(algorithm)
157 BlockSize = blockSize,
166 //The Task Iterator style allows the execution of a sequence of patterns using
168 //This particular variation returns the result of the last task, if there is one
169 public static Task<TResult> Iterate<TResult>(IEnumerable<Task> asyncIterator)
171 if (asyncIterator == null)
172 throw new ArgumentNullException("asyncIterator");
173 var enumerator = asyncIterator.GetEnumerator();
174 if (enumerator == null)
175 throw new InvalidOperationException("Invalid enumerable - GetEnumerator returned null");
177 var tcs = new TaskCompletionSource<TResult>();
178 tcs.Task.ContinueWith(_ => enumerator.Dispose(), TaskContinuationOptions.ExecuteSynchronously);
180 Action<Task> recursiveBody = null;
181 recursiveBody = delegate
185 if (enumerator.MoveNext())
186 enumerator.Current.ContinueWith(recursiveBody,
187 TaskContinuationOptions.ExecuteSynchronously);
190 var lastTask = enumerator.Current as Task<TResult>;
191 var result = (lastTask !=null )
195 tcs.TrySetResult(result);
198 catch (Exception exc)
200 tcs.TrySetException(exc);
208 /* public static byte[] CalculateTopHash(IEnumerable<byte[]> hashMap, string algorithm)
211 throw new ArgumentNullException("hashMap");
212 if (String.IsNullOrWhiteSpace(algorithm))
213 throw new ArgumentNullException("algorithm");
214 Contract.EndContractBlock();
216 var hashCount = hashMap.Count();
219 using (var hasher = HashAlgorithm.Create(algorithm))
222 var count = hashCount;
223 foreach (var block in hashMap)
225 if (i++ != count - 1)
226 hasher.TransformBlock(block, 0, block.Length, null, 0);
228 hasher.TransformFinalBlock(block, 0, block.Length);
231 var finalHash = hasher.Hash;
237 public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
240 throw new ArgumentNullException("hashMap");
241 if (String.IsNullOrWhiteSpace(algorithm))
242 throw new ArgumentNullException("algorithm");
243 Contract.EndContractBlock();
245 var hashCount = hashMap.Count;
246 //The tophash of an empty hashmap is an empty array
249 //The tophash of a one-item hashmap is the hash itself
253 //Calculate the required number of leaf nodes
254 var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
255 //The size of all nodes is the same and equal to the size of the input hashes
256 var hashSize = hashMap[0].Length;
258 //If the hashmap containes fewer nodes than the required leaf count, we need to fill
259 //the rest with empty blocks
261 if (hashCount < leafs)
262 empty = new byte[hashSize];
264 //New hashes will be stored in a dictionary keyed by their step to preserve order
265 var newHashes=new ConcurrentDictionary<int, byte[]>();
267 Parallel.For(0, leafs/2,
270 using (var hasher = HashAlgorithm.Create(algorithm))
273 var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
274 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
276 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
277 hasher.TransformFinalBlock(block2, 0, block2.Length);
279 var finalHash = hasher.Hash;
280 //Store the final value in its proper place
281 newHashes[step] = finalHash;
285 Parallel.For(0, leafs/2,
286 () => HashAlgorithm.Create(algorithm),
287 (step, state, hasher) =>
291 var block1 = i <= hashCount - 1 ? hashMap[i]: empty;
292 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
294 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
295 hasher.TransformFinalBlock(block2, 0, block2.Length);
297 var finalHash = hasher.Hash;
298 //Store the final value in its proper place
299 newHashes[step] = finalHash;
303 hasher => hasher.Dispose());
305 //Extract the hashes to a list ordered by their step
306 var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
307 return CalculateTopHash(hashes, algorithm);
312 /* public static string CalculateTopHash(string hashString, string algorithm)
314 if (String.IsNullOrWhiteSpace(algorithm))
315 throw new ArgumentNullException("algorithm");
316 Contract.EndContractBlock();
317 if (String.IsNullOrWhiteSpace(hashString))
320 using (var hasher = HashAlgorithm.Create(algorithm))
322 var bytes=Encoding.ASCII.GetBytes(hashString.ToLower());
323 var hash=hasher.ComputeHash(bytes);
324 return hash.ToHashString();
328 private static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes=null, int index = 0)
331 throw new ArgumentNullException("stream");
332 if (String.IsNullOrWhiteSpace(algorithm))
333 throw new ArgumentNullException("algorithm");
335 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
337 throw new ArgumentOutOfRangeException("index", "index must be a non-negative value");
338 Contract.EndContractBlock();
342 hashes= new ConcurrentDictionary<int, byte[]>();
344 var buffer = new byte[blockSize];
345 return stream.ReadAsync(buffer, 0, blockSize).ContinueWith(t =>
349 var nextTask = read == blockSize
350 ? CalculateBlockHashesAsync(stream, blockSize, algorithm, hashes, index + 1)
351 : Task.Factory.StartNew(() => hashes);
353 using (var hasher = HashAlgorithm.Create(algorithm))
355 //This code was added for compatibility with the way Pithos calculates the last hash
356 //We calculate the hash only up to the last non-null byte
357 //TODO: Remove if the server starts using the full block instead of the trimmed block
358 var lastByteIndex = Array.FindLastIndex(buffer, read - 1, aByte => aByte != 0);
360 var hash = hasher.ComputeHash(buffer, 0, lastByteIndex+1);
369 public static byte[] CalculateHash(byte[] buffer,string algorithm)
372 throw new ArgumentNullException("buffer");
373 if (String.IsNullOrWhiteSpace(algorithm))
374 throw new ArgumentNullException("algorithm");
375 Contract.EndContractBlock();
377 using (var hasher = HashAlgorithm.Create(algorithm))
379 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);