Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (12.6 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;
51
using System.Threading.Tasks;
52
using System.Linq;
53

    
54
namespace Pithos.Network
55
{
56
    public static class Signature
57
    {
58
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
59
        public const  int BufferSize = 16384;
60

    
61
        public const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";
62
        public const string MERKLE_EMPTY = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
63

    
64

    
65
        public static string CalculateMD5(FileSystemInfo info)
66
        {
67
            if (info==null)
68
                throw new ArgumentNullException("info");
69
            if (String.IsNullOrWhiteSpace(info.FullName))
70
                throw new ArgumentException("info.FullName is empty","info");
71
            Contract.EndContractBlock();
72

    
73
            if (info is DirectoryInfo)
74
                return MD5_EMPTY;
75

    
76
            return CalculateMD5(info.FullName);
77
        }
78

    
79
        public static string CalculateMD5(string path)
80
        {
81
            if (String.IsNullOrWhiteSpace(path))
82
                throw new ArgumentNullException("path");
83
            Contract.EndContractBlock();
84

    
85
            //DON'T calculate hashes for folders
86
            if (Directory.Exists(path))
87
                return "";
88

    
89
            string hash;
90
            using (var hasher = MD5.Create())
91
            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, Signature.BufferSize, true))
92
            {
93
                var hashBytes = hasher.ComputeHash(stream);
94
                hash = hashBytes.ToHashString();
95
            }
96
            return hash;
97
        }
98

    
99
/*
100
        public static string BytesToString(byte[] hashBytes)
101
        {
102
            var shb=new SoapHexBinary(hashBytes);
103
            return shb.ToString();
104
            
105
        }
106

    
107

    
108
        public static byte[] StringToBytes(string hash)
109
        {
110
            var shb=SoapHexBinary.Parse(hash);
111
            return shb.Value;
112
        }
113
*/
114

    
115
        public static byte[] ToBytes(this string hash)
116
        {
117
            var shb = SoapHexBinary.Parse(hash);
118
            return shb.Value;
119
        }
120

    
121
        public static string ToHashString(this byte[] hashBytes)
122
        {
123
            var shb = new SoapHexBinary(hashBytes);
124
            return shb.ToString().ToLower();
125
        }
126

    
127
        public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm,CancellationToken token,IProgress<double> progress )
128
        {
129
            if (fileInfo == null)
130
                throw new ArgumentNullException("fileInfo");
131
            if (String.IsNullOrWhiteSpace(fileInfo.FullName))
132
                throw new ArgumentException("fileInfo.FullName is empty", "fileInfo");
133
            if (blockSize <= 0)
134
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
135
            if (String.IsNullOrWhiteSpace(algorithm))
136
                throw new ArgumentNullException("algorithm");
137
            Contract.EndContractBlock();
138
            fileInfo.Refresh();
139
            if (fileInfo is DirectoryInfo || !fileInfo.Exists)
140
                return TreeHash.Empty;
141

    
142
            return CalculateTreeHash(fileInfo.FullName, blockSize, algorithm,token,progress);
143
        }
144

    
145
        /// <summary>
146
        /// Calculates a file's tree hash synchronously, using the specified block size
147
        /// </summary>
148
        /// <param name="filePath">Path to an existing file</param>
149
        /// <param name="blockSize">Block size used to calculate leaf hashes</param>
150
        /// <param name="algorithm"></param>
151
        /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
152
        public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm,CancellationToken token,IProgress<double> progress )
153
        {
154
            if (String.IsNullOrWhiteSpace(filePath))
155
                throw new ArgumentNullException("filePath");
156
            if (blockSize<=0)
157
                throw new ArgumentOutOfRangeException("blockSize","blockSize must be a value greater than zero ");
158
            if (String.IsNullOrWhiteSpace(algorithm))
159
                throw new ArgumentNullException("algorithm");
160
            Contract.EndContractBlock();           
161
            var hash=CalculateTreeHashAsync(filePath, blockSize, algorithm, 1,token,progress);
162
            return hash;
163
        }
164
        
165
        public static TreeHash CalculateTreeHashAsync(FileInfo fileInfo, int blockSize, string algorithm, byte parallelism,CancellationToken token,IProgress<double> progress )
166
        {
167
            if (fileInfo == null)
168
                throw new ArgumentNullException("fileInfo");
169
            if (String.IsNullOrWhiteSpace(fileInfo.FullName))
170
                throw new ArgumentNullException("fileInfo.FullName is empty","fileInfo");
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
            return CalculateTreeHashAsync(fileInfo.FullName, blockSize, algorithm, parallelism,token,progress);
178
        }
179

    
180

    
181
        public static TreeHash CalculateTreeHashAsync(string filePath, int blockSize,string algorithm, int parallelism,CancellationToken token,IProgress<double> progress )
182
        {
183
            if (String.IsNullOrWhiteSpace(filePath))
184
                throw new ArgumentNullException("filePath");
185
            if (blockSize <= 0)
186
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
187
            if (String.IsNullOrWhiteSpace(algorithm))
188
                throw new ArgumentNullException("algorithm");
189
            Contract.EndContractBlock();
190

    
191
            if (Log.IsDebugEnabled)
192
                Log.DebugFormat("Calc Signature [{0}]",filePath);
193

    
194
            if (filePath.Split('/').Contains(".pithos.cache"))
195
                throw new ArgumentException(String.Format("Trying to hash file from the cache folder: [{0}]",filePath));
196

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

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

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

    
216
                var treeHash = new TreeHash(algorithm)
217
                {
218
                    Bytes = length,
219
                    BlockSize = blockSize,
220
                    Hashes = list,
221
                };
222

    
223
                string fileHash;
224

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

    
232
                return treeHash;
233
            }
234
        }
235

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

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

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

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

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

    
276
                        hasher.TransformBlock(block1, 0, block1.Length, null, 0);
277
                        hasher.TransformFinalBlock(block2, 0, block2.Length);
278

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

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

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

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

    
308

    
309