7016d02c13fc0ba68dc96e77df7002446a0f2921
[pithos-ms-client] / trunk / Pithos.Network / Signature.cs
1 #region
2 /* -----------------------------------------------------------------------
3  * <copyright file="Signature.cs" company="GRNet">
4  * 
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or
8  * without modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *   1. Redistributions of source code must retain the above
12  *      copyright notice, this list of conditions and the following
13  *      disclaimer.
14  *
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.
19  *
20  *
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.
33  *
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.
38  * </copyright>
39  * -----------------------------------------------------------------------
40  */
41 #endregion
42 using System;
43 using System.Collections.Concurrent;
44 using System.Collections.Generic;
45 using System.Diagnostics.Contracts;
46 using System.IO;
47 using System.Reflection;
48 using System.Runtime.Remoting.Metadata.W3cXsd2001;
49 using System.Security.Cryptography;
50 using System.Threading.Tasks;
51 using System.Linq;
52
53 namespace Pithos.Network
54 {
55     public static class Signature
56     {
57         private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
58
59         public const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";
60
61
62         public static string CalculateMD5(FileSystemInfo info)
63         {
64             if (info==null)
65                 throw new ArgumentNullException("info");
66             if (String.IsNullOrWhiteSpace(info.FullName))
67                 throw new ArgumentException("info.FullName is empty","info");
68             Contract.EndContractBlock();
69
70             if (info is DirectoryInfo)
71                 return MD5_EMPTY;
72
73             return CalculateMD5(info.FullName);
74         }
75
76         public static string CalculateMD5(string path)
77         {
78             if (String.IsNullOrWhiteSpace(path))
79                 throw new ArgumentNullException("path");
80             Contract.EndContractBlock();
81
82             //DON'T calculate hashes for folders
83             if (Directory.Exists(path))
84                 return "";
85
86             string hash;
87             using (var hasher = MD5.Create())
88             using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536, true))
89             {
90                 var hashBytes = hasher.ComputeHash(stream);
91                 hash = hashBytes.ToHashString();
92             }
93             return hash;
94         }
95
96 /*
97         public static string BytesToString(byte[] hashBytes)
98         {
99             var shb=new SoapHexBinary(hashBytes);
100             return shb.ToString();
101             
102         }
103
104
105         public static byte[] StringToBytes(string hash)
106         {
107             var shb=SoapHexBinary.Parse(hash);
108             return shb.Value;
109         }
110 */
111
112         public static byte[] ToBytes(this string hash)
113         {
114             var shb = SoapHexBinary.Parse(hash);
115             return shb.Value;
116         }
117
118         public static string ToHashString(this byte[] hashBytes)
119         {
120             var shb = new SoapHexBinary(hashBytes);
121             return shb.ToString().ToLower();
122         }
123
124         public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm,IProgress<double> progress )
125         {
126             if (fileInfo == null)
127                 throw new ArgumentNullException("fileInfo");
128             if (String.IsNullOrWhiteSpace(fileInfo.FullName))
129                 throw new ArgumentException("fileInfo.FullName is empty", "fileInfo");
130             if (blockSize <= 0)
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();
135             fileInfo.Refresh();
136             if (fileInfo is DirectoryInfo || !fileInfo.Exists)
137                 return TreeHash.Empty;
138
139             return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm,progress);
140         }
141
142         /// <summary>
143         /// Calculates a file's tree hash synchronously, using the specified block size
144         /// </summary>
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 )
150         {
151             if (String.IsNullOrWhiteSpace(filePath))
152                 throw new ArgumentNullException("filePath");
153             if (blockSize<=0)
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);
159             return hash;
160         }
161         
162         public static TreeHash CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism,IProgress<double> progress )
163         {
164             if (fileInfo == null)
165                 throw new ArgumentNullException("fileInfo");
166             if (String.IsNullOrWhiteSpace(fileInfo.FullName))
167                 throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
168             if (blockSize <= 0)
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();
173             
174             return CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism,progress);
175         }
176
177
178         public static TreeHash CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism,IProgress<double> progress )
179         {
180             if (String.IsNullOrWhiteSpace(filePath))
181                 throw new ArgumentNullException("filePath");
182             if (blockSize <= 0)
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();
187
188             if (Log.IsDebugEnabled)
189                 Log.DebugFormat("Calc Signature [{0}]",filePath);
190
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);
197
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))
200             {
201                 //Calculate the blocks asyncrhonously
202                 var hashes = BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism,progress).Result;                
203
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();
207
208                 var treeHash = new TreeHash(algorithm)
209                 {
210                     Bytes = length,
211                     BlockSize = blockSize,
212                     Hashes = list,
213                 };
214
215                 string fileHash;
216                 var hasher = HashAlgorithm.Create("MD5");
217                 stream.Position = 0;
218                 treeHash.MD5= hasher.ComputeHash(stream).ToHashString();
219
220                 return treeHash;
221             }
222         }
223
224         
225         public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
226         {
227             if (hashMap == null)
228                 throw new ArgumentNullException("hashMap");
229             if (String.IsNullOrWhiteSpace(algorithm))
230                 throw new ArgumentNullException("algorithm");
231             Contract.EndContractBlock();            
232
233             var hashCount = hashMap.Count;
234             //The tophash of an empty hashmap is an empty array
235             if (hashCount == 0)
236                 return new byte[0];
237             //The tophash of a one-item hashmap is the hash itself
238             if (hashCount == 1)
239                 return hashMap[0];
240
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;
245
246             //If the hashmap containes fewer nodes than the required leaf count, we need to fill
247             //the rest with empty blocks
248             byte[] empty=null;            
249             if (hashCount < leafs)
250                 empty = new byte[hashSize];
251
252             //New hashes will be stored in a dictionary keyed by their step to preserve order
253             var newHashes=new ConcurrentDictionary<int, byte[]>();            
254             
255             Parallel.For(0, leafs/2,
256                 (step, state) =>
257                 {
258                     using (var hasher = HashAlgorithm.Create(algorithm))
259                     {
260                         var i = step*2;
261                         var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
262                         var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
263
264                         hasher.TransformBlock(block1, 0, block1.Length, null, 0);
265                         hasher.TransformFinalBlock(block2, 0, block2.Length);
266
267                         var finalHash = hasher.Hash;
268                         //Store the final value in its proper place
269                         newHashes[step] = finalHash;
270                     }
271                 });
272
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);                   
276         }        
277     
278
279         public static byte[] CalculateHash(byte[] buffer,string algorithm)
280         {
281             if (buffer == null)
282                 throw new ArgumentNullException("buffer");
283             if (String.IsNullOrWhiteSpace(algorithm))
284                 throw new ArgumentNullException("algorithm");
285             Contract.EndContractBlock();
286
287             using (var hasher = HashAlgorithm.Create(algorithm))
288             {
289                 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);
290                 return hash;
291             }        
292         }
293     }
294 }
295
296
297