Modified loggers to use their enclosing class
[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.Runtime.Remoting.Metadata.W3cXsd2001;
48 using System.Security.Cryptography;
49 using System.Threading.Tasks;
50 using System.Linq;
51
52 namespace Pithos.Network
53 {
54     public static class Signature
55     {
56         public static string CalculateMD5(FileInfo info)
57         {
58             if (info==null)
59                 throw new ArgumentNullException("info");
60             if (String.IsNullOrWhiteSpace(info.FullName))
61                 throw new ArgumentException("info.FullName is empty","info");
62             Contract.EndContractBlock();
63
64             return CalculateMD5(info.FullName);
65         }
66
67         public static string CalculateMD5(string path)
68         {
69             if (String.IsNullOrWhiteSpace(path))
70                 throw new ArgumentNullException("path");
71             Contract.EndContractBlock();
72
73             //DON'T calculate hashes for folders
74             if (Directory.Exists(path))
75                 return "";
76
77             string hash;
78             using (var hasher = MD5.Create())
79             using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
80             {
81                 var hashBytes = hasher.ComputeHash(stream);
82                 hash = hashBytes.ToHashString();
83             }
84             return hash;
85         }
86
87 /*
88         public static string BytesToString(byte[] hashBytes)
89         {
90             var shb=new SoapHexBinary(hashBytes);
91             return shb.ToString();
92             
93         }
94
95
96         public static byte[] StringToBytes(string hash)
97         {
98             var shb=SoapHexBinary.Parse(hash);
99             return shb.Value;
100         }
101 */
102
103         public static byte[] ToBytes(this string hash)
104         {
105             var shb = SoapHexBinary.Parse(hash);
106             return shb.Value;
107         }
108
109         public static string ToHashString(this byte[] hashBytes)
110         {
111             var shb = new SoapHexBinary(hashBytes);
112             return shb.ToString().ToLower();
113         }
114
115         public static TreeHash CalculateTreeHash(FileInfo fileInfo, int blockSize, string algorithm)
116         {
117             if (fileInfo==null)
118                 throw new ArgumentNullException("fileInfo");
119             if (String.IsNullOrWhiteSpace(fileInfo.FullName))
120                 throw new ArgumentException("fileInfo.FullName is empty","fileInfo");
121             if (blockSize <= 0)
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();
126
127             return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
128         }
129
130         /// <summary>
131         /// Calculates a file's tree hash synchronously, using the specified block size
132         /// </summary>
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)
138         {
139             if (String.IsNullOrWhiteSpace(filePath))
140                 throw new ArgumentNullException("filePath");
141             if (blockSize<=0)
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();
146
147             var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 2);
148             return hash.Result;
149         }
150         
151         public static async Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism)
152         {
153             if (fileInfo == null)
154                 throw new ArgumentNullException("fileInfo");
155             if (String.IsNullOrWhiteSpace(fileInfo.FullName))
156                 throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
157             if (blockSize <= 0)
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();
162             
163             return await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism);
164         }
165
166
167         public static async Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism)
168         {
169             if (String.IsNullOrWhiteSpace(filePath))
170                 throw new ArgumentNullException("filePath");
171             if (blockSize <= 0)
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();
176
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);
183
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))
186             {
187                 //Calculate the blocks asyncrhonously
188                 var hashes = await BlockHashAlgorithms.CalculateBlockHashesAgentAsync(stream, blockSize, algorithm, parallelism);                
189
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();
193
194                 var treeHash = new TreeHash(algorithm)
195                 {
196                     Bytes = length,
197                     BlockSize = blockSize,
198                     Hashes = list
199                 };
200
201                 return treeHash;
202             }
203         }
204
205         
206         public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
207         {
208             if (hashMap == null)
209                 throw new ArgumentNullException("hashMap");
210             if (String.IsNullOrWhiteSpace(algorithm))
211                 throw new ArgumentNullException("algorithm");
212             Contract.EndContractBlock();            
213
214             var hashCount = hashMap.Count;
215             //The tophash of an empty hashmap is an empty array
216             if (hashCount == 0)
217                 return new byte[0];
218             //The tophash of a one-item hashmap is the hash itself
219             if (hashCount == 1)
220                 return hashMap[0];
221
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;
226
227             //If the hashmap containes fewer nodes than the required leaf count, we need to fill
228             //the rest with empty blocks
229             byte[] empty=null;            
230             if (hashCount < leafs)
231                 empty = new byte[hashSize];
232
233             //New hashes will be stored in a dictionary keyed by their step to preserve order
234             var newHashes=new ConcurrentDictionary<int, byte[]>();            
235             
236             Parallel.For(0, leafs/2,
237                 (step, state) =>
238                 {
239                     using (var hasher = HashAlgorithm.Create(algorithm))
240                     {
241                         var i = step*2;
242                         var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
243                         var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
244
245                         hasher.TransformBlock(block1, 0, block1.Length, null, 0);
246                         hasher.TransformFinalBlock(block2, 0, block2.Length);
247
248                         var finalHash = hasher.Hash;
249                         //Store the final value in its proper place
250                         newHashes[step] = finalHash;
251                     }
252                 });
253
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);                   
257         }        
258     
259
260         public static byte[] CalculateHash(byte[] buffer,string algorithm)
261         {
262             if (buffer == null)
263                 throw new ArgumentNullException("buffer");
264             if (String.IsNullOrWhiteSpace(algorithm))
265                 throw new ArgumentNullException("algorithm");
266             Contract.EndContractBlock();
267
268             using (var hasher = HashAlgorithm.Create(algorithm))
269             {
270                 var hash = hasher.ComputeHash(buffer, 0, buffer.Length);
271                 return hash;
272             }        
273         }
274     }
275 }
276
277
278