Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (11.9 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(string path)
17
        {
18
            if (String.IsNullOrWhiteSpace(path))
19
                throw new ArgumentNullException("path");
20
            Contract.EndContractBlock();
21

    
22
            //DON'T calculate hashes for folders
23
            if (Directory.Exists(path))
24
                return "";
25

    
26
            string hash;
27
            using (var hasher = MD5.Create())
28
            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
29
            {
30
                var hashBytes = hasher.ComputeHash(stream);
31
                hash = hashBytes.ToHashString();
32
            }
33
            return hash;
34
        }
35

    
36
/*
37
        public static string BytesToString(byte[] hashBytes)
38
        {
39
            var shb=new SoapHexBinary(hashBytes);
40
            return shb.ToString();
41
            
42
        }
43

    
44

    
45
        public static byte[] StringToBytes(string hash)
46
        {
47
            var shb=SoapHexBinary.Parse(hash);
48
            return shb.Value;
49
        }
50
*/
51

    
52
        public static byte[] ToBytes(this string hash)
53
        {
54
            var shb = SoapHexBinary.Parse(hash);
55
            return shb.Value;
56
        }
57

    
58
        public static string ToHashString(this byte[] hashBytes)
59
        {
60
            var shb = new SoapHexBinary(hashBytes);
61
            return shb.ToString().ToLower();
62
        }
63

    
64

    
65
        /// <summary>
66
        /// Calculates a file's tree hash synchronously, using the specified block size
67
        /// </summary>
68
        /// <param name="filePath">Path to an existing file</param>
69
        /// <param name="blockSize">Block size used to calculate leaf hashes</param>
70
        /// <param name="algorithm"></param>
71
        /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
72
        public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm)
73
        {
74
            if (String.IsNullOrWhiteSpace(filePath))
75
                throw new ArgumentNullException("filePath");
76
            if (blockSize<=0)
77
                throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
78
            if (String.IsNullOrWhiteSpace(algorithm))
79
                throw new ArgumentNullException("algorithm");
80
            Contract.EndContractBlock();
81

    
82
            //DON'T calculate hashes for folders
83
            if (Directory.Exists(filePath))
84
                return null;
85

    
86

    
87
            var list = new List<byte[]>();
88
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, false))
89
            using (var hasher = HashAlgorithm.Create(algorithm))
90
            {
91
                int read;
92
                var buffer = new byte[blockSize];            
93
                while ((read = stream.Read(buffer, 0, blockSize)) > 0)
94
                {
95
                    var hash = hasher.ComputeHash(buffer, 0, read);
96
                    list.Add(hash);
97
                }
98
                return new TreeHash(algorithm) { Hashes = list,                    
99
                    BlockSize = blockSize, 
100
                    Bytes = stream.Length};
101
            }            
102
        }
103

    
104

    
105
        public static Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize,string algorithm)
106
        {
107
            if (String.IsNullOrWhiteSpace(filePath))
108
                throw new ArgumentNullException("filePath");
109
            if (blockSize <= 0)
110
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
111
            if (String.IsNullOrWhiteSpace(algorithm))
112
                throw new ArgumentNullException("algorithm");
113
            Contract.EndContractBlock();
114

    
115
            //DON'T calculate hashes for folders
116
            if (Directory.Exists(filePath))
117
                return Task.Factory.StartNew(()=>new TreeHash(algorithm));
118

    
119

    
120
            var hashes = new ConcurrentDictionary<int, byte[]>();
121
            var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true);
122

    
123
            return CalculateBlockHashesAsync(stream, blockSize, algorithm,hashes)
124
                .ContinueWith(t => {
125
                                        var length = stream.Length;
126
                                       stream.Close();
127
                                       var list= t.Result.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();                                       
128
                                       return new TreeHash(algorithm) { Hashes = list,                                           
129
                                           BlockSize = blockSize, 
130
                                           Bytes = length };
131
                });
132
        }
133

    
134
      /*  public static byte[] CalculateTopHash(IEnumerable<byte[]> hashMap, string algorithm)
135
        {
136
            if (hashMap == null)
137
                throw new ArgumentNullException("hashMap");
138
            if (String.IsNullOrWhiteSpace(algorithm))
139
                throw new ArgumentNullException("algorithm");
140
            Contract.EndContractBlock();
141

    
142
            var hashCount = hashMap.Count();
143
            if (hashCount == 0)
144
                return null;
145
            using (var hasher = HashAlgorithm.Create(algorithm))
146
            {
147
                var i = 0;
148
                var count = hashCount;
149
                foreach (var block in hashMap)
150
                {
151
                    if (i++ != count - 1)
152
                        hasher.TransformBlock(block, 0, block.Length, null, 0);
153
                    else
154
                        hasher.TransformFinalBlock(block, 0, block.Length);
155
                }
156

    
157
                var finalHash = hasher.Hash;
158

    
159
                return finalHash;
160
            }
161
        }*/
162

    
163
        public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
164
        {
165
            if (hashMap == null)
166
                throw new ArgumentNullException("hashMap");
167
            if (String.IsNullOrWhiteSpace(algorithm))
168
                throw new ArgumentNullException("algorithm");
169
            Contract.EndContractBlock();            
170

    
171
            var hashCount = hashMap.Count;
172
            //The tophash of an empty hashmap is an empty array
173
            if (hashCount == 0)
174
                return new byte[0];
175
            //The tophash of a one-item hashmap is the hash itself
176
            if (hashCount == 1)
177
                return hashMap[0];
178

    
179
            //Calculate the required number of leaf nodes
180
            var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
181
            //The size of all nodes is the same and equal to the size of the input hashes
182
            var hashSize = hashMap[0].Length;
183

    
184
            //If the hashmap containes fewer nodes than the required leaf count, we need to fill
185
            //the rest with empty blocks
186
            byte[] empty=null;            
187
            if (hashCount < leafs)
188
                empty = new byte[hashSize];
189

    
190
            //New hashes will be stored in a dictionary keyed by their step to preserve order
191
            var newHashes=new ConcurrentDictionary<int, byte[]>();            
192
            
193
            Parallel.For(0, leafs/2,
194
                (step, state) =>
195
                {
196
                    using (var hasher = HashAlgorithm.Create(algorithm))
197
                    {
198
                        var i = step*2;
199
                        var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
200
                        var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
201

    
202
                        hasher.TransformBlock(block1, 0, block1.Length, null, 0);
203
                        hasher.TransformFinalBlock(block2, 0, block2.Length);
204

    
205
                        var finalHash = hasher.Hash;
206
                        //Store the final value in its proper place
207
                        newHashes[step] = finalHash;
208
                    }
209
                });
210
/*
211
            Parallel.For(0, leafs/2,
212
                () => HashAlgorithm.Create(algorithm),
213
                (step, state, hasher) =>
214
                {
215
                                 
216
                    var i = step*2;
217
                    var block1 = i <= hashCount - 1 ? hashMap[i]: empty;
218
                    var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
219

    
220
                    hasher.TransformBlock(block1, 0, block1.Length, null, 0);
221
                    hasher.TransformFinalBlock(block2, 0, block2.Length);
222
                    
223
                    var finalHash = hasher.Hash;
224
                    //Store the final value in its proper place
225
                    newHashes[step] = finalHash;
226
                                        
227
                    return hasher;
228
                },
229
                hasher => hasher.Dispose());
230
*/
231
            //Extract the hashes to a list ordered by their step 
232
            var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
233
            return CalculateTopHash(hashes, algorithm);                   
234
        }
235

    
236
        
237

    
238
      /*  public static string CalculateTopHash(string hashString, string algorithm)
239
        {
240
            if (String.IsNullOrWhiteSpace(algorithm))
241
                throw new ArgumentNullException("algorithm");
242
            Contract.EndContractBlock();
243
            if (String.IsNullOrWhiteSpace(hashString))
244
                return String.Empty;
245

    
246
            using (var hasher = HashAlgorithm.Create(algorithm))
247
            {
248
                var bytes=Encoding.ASCII.GetBytes(hashString.ToLower());
249
                var hash=hasher.ComputeHash(bytes);
250
                return hash.ToHashString();
251
            }
252
        }*/
253

    
254
        private static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes, int index = 0)
255
        {
256
            if (stream==null)
257
                throw new ArgumentNullException("stream");
258
            if (hashes==null)
259
                throw new ArgumentNullException("hashes");
260
            if (String.IsNullOrWhiteSpace(algorithm))
261
                throw new ArgumentNullException("algorithm");
262
            if (blockSize <= 0)
263
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
264
            if (index< 0)
265
                throw new ArgumentOutOfRangeException("index", "index must be a non-negative value");
266
            Contract.EndContractBlock();
267

    
268

    
269
            var buffer = new byte[blockSize];
270
            return stream.ReadAsync(buffer, 0, blockSize).ContinueWith(t =>
271
            {
272
                var read = t.Result;
273

    
274
                var nextTask = read == blockSize
275
                                    ? CalculateBlockHashesAsync(stream, blockSize, algorithm, hashes, index + 1) 
276
                                    : Task.Factory.StartNew(() => hashes);
277

    
278
                using (var hasher = HashAlgorithm.Create(algorithm))
279
                {
280
                    var hash = hasher.ComputeHash(buffer, 0, read);
281
                    hashes[index]=hash;
282
                }
283
                return nextTask;
284
            }).Unwrap();
285
        }
286

    
287
    
288

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

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

    
306

    
307