Statistics
| Branch: | Revision:

root / trunk / Pithos.Network / Signature.cs @ 73cdd135

History | View | Annotate | Download (12.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 async 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 await CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm);
124
        }
125

    
126

    
127
        public static async 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 new TreeHash(algorithm);
140
            //The hash of a non-existent file is the empty hash
141
            if (!File.Exists(filePath))
142
                return new TreeHash(algorithm);
143

    
144
            //Calculate the hash of all blocks using a blockhash iterator
145
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, blockSize, true))
146
            {
147
                //Calculate the blocks asyncrhonously
148
                var hashes = await CalculateBlockHashesAsync(stream, blockSize, algorithm);                
149

    
150
                //And then proceed with creating and returning a TreeHash
151
                var length = stream.Length;
152
                var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
153

    
154
                var treeHash = new TreeHash(algorithm)
155
                {
156
                    Bytes = length,
157
                    BlockSize = blockSize,
158
                    Hashes = list
159
                };
160

    
161
                return treeHash;
162
            }
163
        }
164

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

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

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

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

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

    
205
                        hasher.TransformBlock(block1, 0, block1.Length, null, 0);
206
                        hasher.TransformFinalBlock(block2, 0, block2.Length);
207

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

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

    
239
        
240

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

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

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

    
269

    
270
            if (hashes==null)
271
                hashes= new ConcurrentDictionary<int, byte[]>();
272

    
273
            var buffer = new byte[blockSize];
274
            return stream.ReadAsync(buffer, 0, blockSize).ContinueWith(t =>
275
            {
276
                var read = t.Result;
277

    
278
                var nextTask = read == blockSize
279
                                    ? CalculateBlockHashesAsync(stream, blockSize, algorithm, hashes, index + 1) 
280
                                    : Task.Factory.StartNew(() => hashes);
281
                if (read>0)
282
                    using (var hasher = HashAlgorithm.Create(algorithm))
283
                    {
284
                        //This code was added for compatibility with the way Pithos calculates the last hash
285
                        //We calculate the hash only up to the last non-null byte
286
                        //TODO: Remove if the server starts using the full block instead of the trimmed block
287
                        var lastByteIndex = Array.FindLastIndex(buffer, read - 1, aByte => aByte != 0);
288

    
289
                        var hash = hasher.ComputeHash(buffer, 0, lastByteIndex+1);
290
                        hashes[index]=hash;
291
                    }
292
                return nextTask;
293
            }).Unwrap();
294
        }
295

    
296
    
297

    
298
        public static byte[] CalculateHash(byte[] buffer,string algorithm)
299
        {
300
            if (buffer == null)
301
                throw new ArgumentNullException("buffer");
302
            if (String.IsNullOrWhiteSpace(algorithm))
303
                throw new ArgumentNullException("algorithm");
304
            Contract.EndContractBlock();
305

    
306
            using (var hasher = HashAlgorithm.Create(algorithm))
307
            {
308
                var hash = hasher.ComputeHash(buffer, 0, buffer.Length);
309
                return hash;
310
            }        
311
        }
312
    }
313
}
314

    
315

    
316