Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (15.6 kB)

1
using System;
2
using System.Collections.Concurrent;
3
using System.Collections.Generic;
4
using System.Diagnostics.Contracts;
5
using System.IO;
6
using System.Runtime.Remoting.Metadata.W3cXsd2001;
7
using System.Security.Cryptography;
8
using System.Text;
9
using System.Threading.Tasks;
10
using System.Linq;
11

    
12
namespace Pithos.Network
13
{
14
    public static class Signature
15
    {
16
        public static string CalculateMD5(FileInfo info)
17
        {
18
            if (info==null)
19
                throw new ArgumentNullException("info");
20
            if (String.IsNullOrWhiteSpace(info.FullName))
21
                throw new ArgumentException("info.FullName is empty","info");
22
            Contract.EndContractBlock();
23

    
24
            return CalculateMD5(info.FullName);
25
        }
26

    
27
        public static string CalculateMD5(string path)
28
        {
29
            if (String.IsNullOrWhiteSpace(path))
30
                throw new ArgumentNullException("path");
31
            Contract.EndContractBlock();
32

    
33
            //DON'T calculate hashes for folders
34
            if (Directory.Exists(path))
35
                return "";
36

    
37
            string hash;
38
            using (var hasher = MD5.Create())
39
            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
40
            {
41
                var hashBytes = hasher.ComputeHash(stream);
42
                hash = hashBytes.ToHashString();
43
            }
44
            return hash;
45
        }
46

    
47
/*
48
        public static string BytesToString(byte[] hashBytes)
49
        {
50
            var shb=new SoapHexBinary(hashBytes);
51
            return shb.ToString();
52
            
53
        }
54

    
55

    
56
        public static byte[] StringToBytes(string hash)
57
        {
58
            var shb=SoapHexBinary.Parse(hash);
59
            return shb.Value;
60
        }
61
*/
62

    
63
        public static byte[] ToBytes(this string hash)
64
        {
65
            var shb = SoapHexBinary.Parse(hash);
66
            return shb.Value;
67
        }
68

    
69
        public static string ToHashString(this byte[] hashBytes)
70
        {
71
            var shb = new SoapHexBinary(hashBytes);
72
            return shb.ToString().ToLower();
73
        }
74

    
75
        public static TreeHash CalculateTreeHash(FileInfo fileInfo, int blockSize, string algorithm)
76
        {
77
            if (fileInfo==null)
78
                throw new ArgumentNullException("fileInfo");
79
            if (String.IsNullOrWhiteSpace(fileInfo.FullName))
80
                throw new ArgumentException("fileInfo.FullName is empty","fileInfo");
81
            if (blockSize <= 0)
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();
86

    
87
            return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm);
88
        }
89

    
90
        /// <summary>
91
        /// Calculates a file's tree hash synchronously, using the specified block size
92
        /// </summary>
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)
98
        {
99
            if (String.IsNullOrWhiteSpace(filePath))
100
                throw new ArgumentNullException("filePath");
101
            if (blockSize<=0)
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();
106

    
107
            var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm);
108
            return hash.Result;
109
        }
110
        
111
        public static Task<TreeHash> CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm)
112
        {
113
            if (fileInfo == null)
114
                throw new ArgumentNullException("fileInfo");
115
            if (String.IsNullOrWhiteSpace(fileInfo.FullName))
116
                throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
117
            if (blockSize <= 0)
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();
122

    
123
            return CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm);
124
        }
125

    
126

    
127
        public static Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm)
128
        {
129
            if (String.IsNullOrWhiteSpace(filePath))
130
                throw new ArgumentNullException("filePath");
131
            if (blockSize <= 0)
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();
136

    
137
            //DON'T calculate hashes for folders
138
            if (Directory.Exists(filePath))
139
                return Task.Factory.StartNew(()=>new TreeHash(algorithm));
140
            //The hash of a non-existent file is the empty hash
141
            if (!File.Exists(filePath))
142
                return Task.Factory.StartNew(()=>new TreeHash(algorithm));
143

    
144
            //Calculate the hash of all blocks using a blockhash iterator
145
            var treeHash =Iterate<TreeHash>(BlockHashIterator(filePath, blockSize, algorithm));
146
            
147
            return treeHash;
148
        }
149

    
150
        
151
        private static IEnumerable<Task> BlockHashIterator(string filePath, int blockSize, string algorithm)
152
        {
153
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
154
            {
155
                //Calculate the blocks asyncrhonously
156
                var hashes= CalculateBlockHashesAsync(stream, blockSize, algorithm);
157
                yield return hashes;
158
                
159
                //And then proceed with creating and returning a TreeHash
160
                var length = stream.Length;                
161
                var list = hashes.Result.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
162

    
163
                var treeHash = new TreeHash(algorithm)
164
                {
165
                    Bytes = length, 
166
                    BlockSize = blockSize, 
167
                    Hashes = list
168
                };
169

    
170
                yield return Task.Factory.FromResult(treeHash);
171
            }
172

    
173
        }
174

    
175
        //The Task Iterator style allows the execution of a sequence of patterns using
176
        //iterator syntax.
177
        //This particular variation returns the result of the last task, if there is one
178
        public static Task<TResult> Iterate<TResult>(IEnumerable<Task> asyncIterator)
179
        {
180
            if (asyncIterator == null) 
181
                throw new ArgumentNullException("asyncIterator");
182
            var enumerator = asyncIterator.GetEnumerator();
183
            if (enumerator == null)
184
                throw new InvalidOperationException("Invalid enumerable - GetEnumerator returned null");
185
            
186
            var tcs = new TaskCompletionSource<TResult>();
187
            tcs.Task.ContinueWith(_ => enumerator.Dispose(), TaskContinuationOptions.ExecuteSynchronously);
188
            
189
            Action<Task> recursiveBody = null;
190
            recursiveBody = delegate
191
            {
192
                try
193
                {
194
                    if (enumerator.MoveNext())
195
                        enumerator.Current.ContinueWith(recursiveBody,
196
                                                        TaskContinuationOptions.ExecuteSynchronously);
197
                    else
198
                    {
199
                        var lastTask = enumerator.Current as Task<TResult>;
200
                        var result = (lastTask !=null ) 
201
                            ? lastTask.Result
202
                            : default(TResult);
203

    
204
                        tcs.TrySetResult(result);
205
                    }
206
                }
207
                catch (Exception exc)
208
                {
209
                    tcs.TrySetException(exc);
210
                }
211
            };
212
            recursiveBody(null);
213
            return tcs.Task;
214
        }
215

    
216

    
217
        /*  public static byte[] CalculateTopHash(IEnumerable<byte[]> hashMap, string algorithm)
218
        {
219
            if (hashMap == null)
220
                throw new ArgumentNullException("hashMap");
221
            if (String.IsNullOrWhiteSpace(algorithm))
222
                throw new ArgumentNullException("algorithm");
223
            Contract.EndContractBlock();
224

    
225
            var hashCount = hashMap.Count();
226
            if (hashCount == 0)
227
                return null;
228
            using (var hasher = HashAlgorithm.Create(algorithm))
229
            {
230
                var i = 0;
231
                var count = hashCount;
232
                foreach (var block in hashMap)
233
                {
234
                    if (i++ != count - 1)
235
                        hasher.TransformBlock(block, 0, block.Length, null, 0);
236
                    else
237
                        hasher.TransformFinalBlock(block, 0, block.Length);
238
                }
239

    
240
                var finalHash = hasher.Hash;
241

    
242
                return finalHash;
243
            }
244
        }*/
245

    
246
        public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
247
        {
248
            if (hashMap == null)
249
                throw new ArgumentNullException("hashMap");
250
            if (String.IsNullOrWhiteSpace(algorithm))
251
                throw new ArgumentNullException("algorithm");
252
            Contract.EndContractBlock();            
253

    
254
            var hashCount = hashMap.Count;
255
            //The tophash of an empty hashmap is an empty array
256
            if (hashCount == 0)
257
                return new byte[0];
258
            //The tophash of a one-item hashmap is the hash itself
259
            if (hashCount == 1)
260
                return hashMap[0];
261

    
262
            //Calculate the required number of leaf nodes
263
            var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
264
            //The size of all nodes is the same and equal to the size of the input hashes
265
            var hashSize = hashMap[0].Length;
266

    
267
            //If the hashmap containes fewer nodes than the required leaf count, we need to fill
268
            //the rest with empty blocks
269
            byte[] empty=null;            
270
            if (hashCount < leafs)
271
                empty = new byte[hashSize];
272

    
273
            //New hashes will be stored in a dictionary keyed by their step to preserve order
274
            var newHashes=new ConcurrentDictionary<int, byte[]>();            
275
            
276
            Parallel.For(0, leafs/2,
277
                (step, state) =>
278
                {
279
                    using (var hasher = HashAlgorithm.Create(algorithm))
280
                    {
281
                        var i = step*2;
282
                        var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
283
                        var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
284

    
285
                        hasher.TransformBlock(block1, 0, block1.Length, null, 0);
286
                        hasher.TransformFinalBlock(block2, 0, block2.Length);
287

    
288
                        var finalHash = hasher.Hash;
289
                        //Store the final value in its proper place
290
                        newHashes[step] = finalHash;
291
                    }
292
                });
293
/*
294
            Parallel.For(0, leafs/2,
295
                () => HashAlgorithm.Create(algorithm),
296
                (step, state, hasher) =>
297
                {
298
                                 
299
                    var i = step*2;
300
                    var block1 = i <= hashCount - 1 ? hashMap[i]: empty;
301
                    var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
302

    
303
                    hasher.TransformBlock(block1, 0, block1.Length, null, 0);
304
                    hasher.TransformFinalBlock(block2, 0, block2.Length);
305
                    
306
                    var finalHash = hasher.Hash;
307
                    //Store the final value in its proper place
308
                    newHashes[step] = finalHash;
309
                                        
310
                    return hasher;
311
                },
312
                hasher => hasher.Dispose());
313
*/
314
            //Extract the hashes to a list ordered by their step 
315
            var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
316
            return CalculateTopHash(hashes, algorithm);                   
317
        }
318

    
319
        
320

    
321
      /*  public static string CalculateTopHash(string hashString, string algorithm)
322
        {
323
            if (String.IsNullOrWhiteSpace(algorithm))
324
                throw new ArgumentNullException("algorithm");
325
            Contract.EndContractBlock();
326
            if (String.IsNullOrWhiteSpace(hashString))
327
                return String.Empty;
328

    
329
            using (var hasher = HashAlgorithm.Create(algorithm))
330
            {
331
                var bytes=Encoding.ASCII.GetBytes(hashString.ToLower());
332
                var hash=hasher.ComputeHash(bytes);
333
                return hash.ToHashString();
334
            }
335
        }*/
336

    
337
        private static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes=null, int index = 0)
338
        {
339
            if (stream==null)
340
                throw new ArgumentNullException("stream");
341
            if (String.IsNullOrWhiteSpace(algorithm))
342
                throw new ArgumentNullException("algorithm");
343
            if (blockSize <= 0)
344
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
345
            if (index< 0)
346
                throw new ArgumentOutOfRangeException("index", "index must be a non-negative value");
347
            Contract.EndContractBlock();
348

    
349

    
350
            if (hashes==null)
351
                hashes= new ConcurrentDictionary<int, byte[]>();
352

    
353
            var buffer = new byte[blockSize];
354
            return stream.ReadAsync(buffer, 0, blockSize).ContinueWith(t =>
355
            {
356
                var read = t.Result;
357

    
358
                var nextTask = read == blockSize
359
                                    ? CalculateBlockHashesAsync(stream, blockSize, algorithm, hashes, index + 1) 
360
                                    : Task.Factory.StartNew(() => hashes);
361
                if (read>0)
362
                    using (var hasher = HashAlgorithm.Create(algorithm))
363
                    {
364
                        //This code was added for compatibility with the way Pithos calculates the last hash
365
                        //We calculate the hash only up to the last non-null byte
366
                        //TODO: Remove if the server starts using the full block instead of the trimmed block
367
                        var lastByteIndex = Array.FindLastIndex(buffer, read - 1, aByte => aByte != 0);
368

    
369
                        var hash = hasher.ComputeHash(buffer, 0, lastByteIndex+1);
370
                        hashes[index]=hash;
371
                    }
372
                return nextTask;
373
            }).Unwrap();
374
        }
375

    
376
    
377

    
378
        public static byte[] CalculateHash(byte[] buffer,string algorithm)
379
        {
380
            if (buffer == null)
381
                throw new ArgumentNullException("buffer");
382
            if (String.IsNullOrWhiteSpace(algorithm))
383
                throw new ArgumentNullException("algorithm");
384
            Contract.EndContractBlock();
385

    
386
            using (var hasher = HashAlgorithm.Create(algorithm))
387
            {
388
                var hash = hasher.ComputeHash(buffer, 0, buffer.Length);
389
                return hash;
390
            }        
391
        }
392
    }
393
}
394

    
395

    
396