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 public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
169 throw new ArgumentNullException("hashMap");
170 if (String.IsNullOrWhiteSpace(algorithm))
171 throw new ArgumentNullException("algorithm");
172 Contract.EndContractBlock();
174 var hashCount = hashMap.Count;
175 //The tophash of an empty hashmap is an empty array
178 //The tophash of a one-item hashmap is the hash itself
182 //Calculate the required number of leaf nodes
183 var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
184 //The size of all nodes is the same and equal to the size of the input hashes
185 var hashSize = hashMap[0].Length;
187 //If the hashmap containes fewer nodes than the required leaf count, we need to fill
188 //the rest with empty blocks
190 if (hashCount < leafs)
191 empty = new byte[hashSize];
193 //New hashes will be stored in a dictionary keyed by their step to preserve order
194 var newHashes=new ConcurrentDictionary<int, byte[]>();
196 Parallel.For(0, leafs/2,
199 using (var hasher = HashAlgorithm.Create(algorithm))
202 var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
203 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
205 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
206 hasher.TransformFinalBlock(block2, 0, block2.Length);
208 var finalHash = hasher.Hash;
209 //Store the final value in its proper place
210 newHashes[step] = finalHash;
214 Parallel.For(0, leafs/2,
215 () => HashAlgorithm.Create(algorithm),
216 (step, state, hasher) =>
220 var block1 = i <= hashCount - 1 ? hashMap[i]: empty;
221 var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
223 hasher.TransformBlock(block1, 0, block1.Length, null, 0);
224 hasher.TransformFinalBlock(block2, 0, block2.Length);
226 var finalHash = hasher.Hash;
227 //Store the final value in its proper place
228 newHashes[step] = finalHash;
232 hasher => hasher.Dispose());
234 //Extract the hashes to a list ordered by their step
235 var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
236 return CalculateTopHash(hashes, algorithm);
241 /* public static string CalculateTopHash(string hashString, string algorithm)
243 if (String.IsNullOrWhiteSpace(algorithm))
244 throw new ArgumentNullException("algorithm");
245 Contract.EndContractBlock();
246 if (String.IsNullOrWhiteSpace(hashString))
249 using (var hasher = HashAlgorithm.Create(algorithm))
251 var bytes=Encoding.ASCII.GetBytes(hashString.ToLower());
252 var hash=hasher.ComputeHash(bytes);
253 return hash.ToHashString();
257 private static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes=null, int index = 0)
260 throw new ArgumentNullException("stream");
261 if (String.IsNullOrWhiteSpace(algorithm))
262 throw new ArgumentNullException("algorithm");
264 throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
266 throw new ArgumentOutOfRangeException("index", "index must be a non-negative value");
267 Contract.EndContractBlock();
271 hashes= new ConcurrentDictionary<int, byte[]>();
273 var buffer = new byte[blockSize];
274 return stream.ReadAsync(buffer, 0, blockSize).ContinueWith(t =>
278 var nextTask = read == blockSize
279 ? CalculateBlockHashesAsync(stream, blockSize, algorithm, hashes, index + 1)
280 : Task.Factory.StartNew(() => hashes);
282 using (var hasher = HashAlgorithm.Create(algorithm))
284 //This code was added for compatibility with the way Pithos calculates the last hash
285 //We calculate the hash only up to the last non-null byte
286 //TODO: Remove if the server starts using the full block instead of the trimmed block
287 var lastByteIndex = Array.FindLastIndex(buffer, read - 1, aByte => aByte != 0);
289 var hash = hasher.ComputeHash(buffer, 0, lastByteIndex+1);
298 public static byte[] CalculateHash(byte[] buffer,string algorithm)
301 throw new ArgumentNullException("buffer");
302 if (String.IsNullOrWhiteSpace(algorithm))
303 throw new ArgumentNullException("algorithm");
304 Contract.EndContractBlock();
306 using (var hasher = HashAlgorithm.Create(algorithm))
308 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);