Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (12.5 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.Threading;
50
using System.Threading.Tasks;
51
using System.Linq;
52
using Pithos.Interfaces;
53
using log4net;
54
using OpenSSL.Core;
55
using OpenSSL.Crypto;
56

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

    
64
//        public const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";
65
        public const string MERKLE_EMPTY = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
66

    
67

    
68
        public static byte[] ToBytes(this string hash)
69
        {
70
            var shb = SoapHexBinary.Parse(hash);
71
            return shb.Value;
72
        }
73

    
74
        public static string ToHashString(this byte[] hashBytes)
75
        {
76
            var shb = new SoapHexBinary(hashBytes);
77
            return shb.ToString().ToLower();
78
        }
79

    
80
        public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, int blockSize, string algorithm,byte parallelism,CancellationToken token,IProgress<HashProgress> progress )
81
        {
82
            if (fileInfo == null)
83
                throw new ArgumentNullException("fileInfo");
84
            if (String.IsNullOrWhiteSpace(fileInfo.FullName))
85
                throw new ArgumentException("fileInfo.FullName is empty", "fileInfo");
86
            if (blockSize <= 0)
87
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
88
            if (String.IsNullOrWhiteSpace(algorithm))
89
                throw new ArgumentNullException("algorithm");
90
            Contract.EndContractBlock();
91
            fileInfo.Refresh();
92
            if (fileInfo is DirectoryInfo || !fileInfo.Exists)
93
                return TreeHash.Empty;
94

    
95
            return TaskEx.Run(async ()=>await CalculateTreeHashAsync(fileInfo, blockSize, algorithm, parallelism, token, progress)).Result;
96
        }
97

    
98
        /// <summary>
99
        /// Calculates a file's tree hash synchronously, using the specified block size
100
        /// </summary>
101
        /// <param name="filePath">Path to an existing file</param>
102
        /// <param name="blockSize">Block size used to calculate leaf hashes</param>
103
        /// <param name="algorithm"></param>
104
        /// <returns>A <see cref="TreeHash"/> with the block hashes and top hash</returns>
105
        public static TreeHash CalculateTreeHash(string filePath, int blockSize, string algorithm,CancellationToken token,IProgress<HashProgress> progress )
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
            var info = FileInfoExtensions.FromPath(filePath);
116
            var hash=TaskEx.Run(async ()=>await CalculateTreeHashAsync(info, blockSize, algorithm, 1,token,progress).ConfigureAwait(false)).Result;
117
            return hash;
118
        }
119
        
120

    
121
        public static Task<TreeHash> CalculateTreeHashAsync(string filePath, int blockSize, string algorithm, byte parallelism,CancellationToken token,IProgress<HashProgress> progress )
122
        {
123
            if (filePath== null)
124
                throw new ArgumentNullException("filePath");
125
            if (blockSize <= 0)
126
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
127
            if (String.IsNullOrWhiteSpace(algorithm))
128
                throw new ArgumentNullException("algorithm");
129
            Contract.EndContractBlock();
130

    
131
            var info = FileInfoExtensions.FromPath(filePath);
132
            return CalculateTreeHashAsync(info, blockSize, algorithm, parallelism,token,progress);
133
        }
134

    
135

    
136

    
137
        public static async Task<TreeHash> CalculateTreeHashAsync(FileSystemInfo info, int blockSize,string algorithm, byte parallelism,CancellationToken token,IProgress<HashProgress> progress )
138
        {
139
            if (info==null)
140
                throw new ArgumentNullException("info");
141
            if (blockSize <= 0)
142
                throw new ArgumentOutOfRangeException("blockSize", "blockSize must be a value greater than zero ");
143
            if (String.IsNullOrWhiteSpace(algorithm))
144
                throw new ArgumentNullException("algorithm");
145
            Contract.EndContractBlock();
146

    
147
            var filePath = info.FullName;
148

    
149
            if (Log.IsDebugEnabled)
150
                Log.DebugFormat("Calc Signature [{0}]",filePath);
151

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

    
155
            //DON'T calculate hashes for folders
156
            if (Directory.Exists(filePath))
157
                return new TreeHash(algorithm);
158
            //The hash of a non-existent file is the empty hash
159
            if (!File.Exists(filePath))
160
                return new TreeHash(algorithm);            
161

    
162
            if (info is FileInfo && (info as FileInfo).Length==0)
163
                return new TreeHash(algorithm);
164

    
165
            //Calculate the hash of all blocks using a blockhash iterator
166
            using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, BufferSize, true))
167
            {
168
                //var md5 = new MD5BlockCalculator();
169
                //Action<long, byte[], int> postAction = md5.PostBlock;
170
                //Calculate the blocks asyncrhonously
171
                var hashes = await BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism,token, progress).ConfigureAwait(false);
172
                //var hashes = BlockHashAlgorithms.CalculateBlockHashesInPlacePFor(stream, blockSize, algorithm, parallelism, postAction, token, progress).Result; 
173

    
174
                //And then proceed with creating and returning a TreeHash
175
                var length = stream.Length;
176
                var list = hashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
177

    
178
                var treeHash = new TreeHash(algorithm)
179
                {
180
                    Bytes = length,
181
                    BlockSize = blockSize,
182
                    Hashes = list,
183
                };
184

    
185
                //string fileHash;
186

    
187
                //var md5Hash=md5.GetHash().Result;
188
                //treeHash.MD5= md5Hash;
189

    
190
                return treeHash;
191
            }
192
        }
193

    
194
        
195
        public static byte[] CalculateTopHash(IList<byte[]> hashMap, string algorithm)
196
        {
197
            if (hashMap == null)
198
                throw new ArgumentNullException("hashMap");
199
            if (String.IsNullOrWhiteSpace(algorithm))
200
                throw new ArgumentNullException("algorithm");
201
            Contract.EndContractBlock();            
202

    
203
            var hashCount = hashMap.Count;
204
            //TODO: Replace this calculation with a constant
205
            //The tophash of an empty hashmap is the hash of an empty array
206
            if (hashCount == 0)
207
            {
208
                using (var hasher = new MessageDigestContext(MessageDigest.CreateByName(algorithm)))
209
                {
210
                    hasher.Init();
211
                    var emptyHash=hasher.Digest(new byte[0]);
212
                    return emptyHash;
213
                }                
214
            }
215
            //The tophash of a one-item hashmap is the hash itself
216
            if (hashCount == 1)
217
                return hashMap[0];
218

    
219
            //Calculate the required number of leaf nodes
220
            var leafs =(int)Math.Pow(2, Math.Ceiling(Math.Log(hashCount,2)));
221
            //The size of all nodes is the same and equal to the size of the input hashes
222
            var hashSize = hashMap[0].Length;
223

    
224
            //If the hashmap containes fewer nodes than the required leaf count, we need to fill
225
            //the rest with empty blocks
226
            byte[] empty=null;            
227
            if (hashCount < leafs)
228
                empty = new byte[hashSize];
229

    
230
            //New hashes will be stored in a dictionary keyed by their step to preserve order
231
            var newHashes=new ConcurrentDictionary<int, byte[]>();            
232
            
233
            Parallel.For(0, leafs/2,
234
                (step, state) =>
235
                {
236
                    using (var hasher = new MessageDigestContext(MessageDigest.CreateByName(algorithm)))
237
                    {
238
                        hasher.Init();
239
                        var i = step*2;
240
                        var block1 = i <= hashCount - 1 ? hashMap[i] : empty;
241
                        var block2 = i <= hashCount - 2 ? hashMap[i + 1] : empty;
242

    
243
                        hasher.Update(block1);
244
                        hasher.Update(block2);
245

    
246
                        var finalHash = hasher.DigestFinal();
247
                        //Store the final value in its proper place
248
                        newHashes[step] = finalHash;
249
                    }
250
                });
251

    
252
            //Extract the hashes to a list ordered by their step 
253
            var hashes = newHashes.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToList();
254
            return CalculateTopHash(hashes, algorithm);                   
255
        }        
256
    
257

    
258
        public static byte[] CalculateHash(byte[] buffer,string algorithm)
259
        {
260
            if (buffer == null)
261
                throw new ArgumentNullException("buffer");
262
            if (String.IsNullOrWhiteSpace(algorithm))
263
                throw new ArgumentNullException("algorithm");
264
            Contract.EndContractBlock();
265

    
266
            using (var hasher = new MessageDigestContext(MessageDigest.CreateByName(algorithm)))
267
            {
268
                hasher.Init();
269
                var hash = hasher.Digest(buffer);
270
                return hash;
271
            }        
272
        }
273
    }
274

    
275
    public class HashProgress
276
    {
277
        public HashProgress(long current,long total)
278
        {
279
            Current = current;
280
            Total = total;
281

    
282
        }
283
        public float Percentage { get
284
        {
285
            if (Current > Total)
286
                return 1;
287
            return Current/(float)Total;
288
        }}
289
        public long Current { get; set; }
290
        public long Total { get; set; }
291

    
292
    }
293

    
294
}
295

    
296

    
297