Statistics
| Branch: | Revision:

root / trunk / Pithos.Network / Signature.cs @ e1fa1d21

History | View | Annotate | Download (12.2 kB)

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
            if (filePath.Split('/').Contains(".pithos.cache"))
192
                throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]",filePath));
193

    
194
            //DON'T calculate hashes for folders
195
            if (Directory.Exists(filePath))
196
                return new TreeHash(algorithm);
197
            //The hash of a non-existent file is the empty hash
198
            if (!File.Exists(filePath))
199
                return new TreeHash(algorithm);
200

    
201
            //Calculate the hash of all blocks using a blockhash iterator
202
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
203
            {
204
                var md5 = new MD5BlockCalculator();
205
                Action<long, byte[], int> postAction = md5.PostBlock;
206
                //Calculate the blocks asyncrhonously
207
                var hashes = BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism,postAction, progress).Result;                
208

    
209
                //And then proceed with creating and returning a TreeHash
210
                var length = stream.Length;
211
                var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
212

    
213
                var treeHash = new TreeHash(algorithm)
214
                {
215
                    Bytes = length,
216
                    BlockSize = blockSize,
217
                    Hashes = list,
218
                };
219

    
220
                string fileHash;
221

    
222
                var md5Hash=md5.GetHash().Result;
223
/*
224
                var hasher = HashAlgorithm.Create("MD5");
225
                stream.Position = 0;
226
*/
227
                treeHash.MD5= md5Hash;
228

    
229
                return treeHash;
230
            }
231
        }
232

    
233
        
234
        public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
235
        {
236
            if (hashMap == null)
237
                throw new ArgumentNullException("hashMap");
238
            if (String.IsNullOrWhiteSpace(algorithm))
239
                throw new ArgumentNullException("algorithm");
240
            Contract.EndContractBlock();            
241

    
242
            var hashCount = hashMap.Count;
243
            //The tophash of an empty hashmap is an empty array
244
            if (hashCount == 0)
245
                return new byte[0];
246
            //The tophash of a one-item hashmap is the hash itself
247
            if (hashCount == 1)
248
                return hashMap[0];
249

    
250
            //Calculate the required number of leaf nodes
251
            var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
252
            //The size of all nodes is the same and equal to the size of the input hashes
253
            var hashSize = hashMap[0].Length;
254

    
255
            //If the hashmap containes fewer nodes than the required leaf count, we need to fill
256
            //the rest with empty blocks
257
            byte[] empty=null;            
258
            if (hashCount < leafs)
259
                empty = new byte[hashSize];
260

    
261
            //New hashes will be stored in a dictionary keyed by their step to preserve order
262
            var newHashes=new ConcurrentDictionary<int, byte[]>();            
263
            
264
            Parallel.For(0, leafs/2,
265
                (step, state) =>
266
                {
267
                    using (var hasher = HashAlgorithm.Create(algorithm))
268
                    {
269
                        var i = step*2;
270
                        var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
271
                        var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
272

    
273
                        hasher.TransformBlock(block1, 0, block1.Length, null, 0);
274
                        hasher.TransformFinalBlock(block2, 0, block2.Length);
275

    
276
                        var finalHash = hasher.Hash;
277
                        //Store the final value in its proper place
278
                        newHashes[step] = finalHash;
279
                    }
280
                });
281

    
282
            //Extract the hashes to a list ordered by their step 
283
            var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
284
            return CalculateTopHash(hashes, algorithm);                   
285
        }        
286
    
287

    
288
        public static byte[] CalculateHash(byte[] buffer,string algorithm)
289
        {
290
            if (buffer == null)
291
                throw new ArgumentNullException("buffer");
292
            if (String.IsNullOrWhiteSpace(algorithm))
293
                throw new ArgumentNullException("algorithm");
294
            Contract.EndContractBlock();
295

    
296
            using (var hasher = HashAlgorithm.Create(algorithm))
297
            {
298
                var hash = hasher.ComputeHash(buffer, 0, buffer.Length);
299
                return hash;
300
            }        
301
        }
302
    }
303
}
304

    
305

    
306