Statistics
| Branch: | Revision:

root / trunk / Pithos.Network / TreeHash.cs @ d78d765c

History | View | Annotate | Download (7.9 kB)

1
#region
2
/* -----------------------------------------------------------------------
3
 * <copyright file="TreeHash.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.Text;
48
using System.Threading.Tasks;
49

    
50
using System.Linq;
51
using Newtonsoft.Json;
52
using Newtonsoft.Json.Linq;
53

    
54
namespace Pithos.Network
55
{
56
    public class TreeHash
57
    {
58
        private const string DEFAULT_HASH_ALGORITHM = "sha256";
59
        private const int DEFAULT_BLOCK_SIZE = 4*1024*1024;
60
        public string BlockHash { get; set; }
61
        public int BlockSize { get; set; }
62
        
63
        private long _bytes;
64
        public long Bytes
65
        {
66
            get
67
            {
68
                Contract.Ensures(Contract.Result<long>() >= 0);
69
                return _bytes;
70
            }
71
            set
72
            {
73
                if (value<0)
74
                    throw new ArgumentOutOfRangeException("Bytes");
75
                Contract.Requires(value >= 0);
76
                Contract.EndContractBlock();
77
                
78
                _bytes = value;
79
            }
80
        }
81
        
82

    
83

    
84
        public Guid FileId { get; set; }
85

    
86
        private readonly Lazy<byte[]> _topHash;        
87
        public byte[] TopHash
88
        {
89
            get { return _topHash.Value; }
90
        }
91

    
92
        private IList<byte[]> _hashes=new List<byte[]>();
93

    
94
        public IList<byte[]> Hashes
95
        {
96
            get { return _hashes; }
97
            set
98
            {
99
                _hashes = value;
100
                _topHash.Force();
101
            }
102
        }
103

    
104
        [ContractInvariantMethod]
105
        private void Invariants()
106
        {
107
            Contract.Invariant(_bytes>=0);
108
        }
109
        
110

    
111
       public TreeHash(string algorithm)
112
        {
113
            BlockHash = algorithm;            
114
            _topHash = new Lazy<byte[]>(() =>
115
            {
116
                //
117
                //Cast the hashes to an IList or create a new list out of the hashes
118
                var hashMap = (_hashes as IList<byte[]>)??_hashes.ToList();
119
                return Signature.CalculateTopHash(hashMap, BlockHash);
120
            });
121
        }
122

    
123
        //Returns a Json representation of the hashes, as required by Pithos
124
        public string ToJson()
125
        {
126
            var value = new JObject();
127
            //We avoid using JObject's dynamic features because they use exceptions to detect new properties.
128
            value["block_hash"] = BlockHash;
129
            //Create a string array for all the hashes           
130
            
131
            string[] hashes=null ;
132
            if (Hashes!=null)
133
                hashes= GetHashesAsStrings();
134
            value["hashes"]= new JArray(hashes);
135
            value["block_size"] = BlockSize;
136
            value["bytes"] = Bytes;
137
            
138
            var json = JsonConvert.SerializeObject(value, Formatting.None);
139
            return json;
140
        }
141

    
142
        private string[] _stringHashes;
143
        //Returns the hashes as an array of hash strings. Used for serialization to Json
144
        public string[] GetHashesAsStrings()
145
        {
146
            return _stringHashes 
147
                ?? (_stringHashes = Hashes.Select(hash => hash.ToHashString()).ToArray());
148
        }
149

    
150
        ConcurrentDictionary<string, int> _blocks;
151
        //Retruns the hashes as a dictionary to the block location. Used to locate blocks
152
        public IDictionary<string,int> HashDictionary
153
        {
154
            get
155
            {
156
                Func<ConcurrentDictionary<string, int>> blocksInit = () =>
157
                {
158
                    var blocks =
159
                        new ConcurrentDictionary<string, int>();
160
                    if (Hashes == null)
161
                        return blocks;
162

    
163
                    var blockIndex = 0;
164
                    foreach (var hash in this.Hashes)
165
                    {
166
                        blocks[hash.ToHashString()] = blockIndex++;
167
                    }
168
                    return blocks;
169
                };
170

    
171
                return _blocks ?? (_blocks = blocksInit());
172
            }
173
        }
174

    
175
        //Saves the Json representation to a file
176
        public async Task Save(string filePath)
177
        {
178
            if (String.IsNullOrWhiteSpace(filePath))
179
                throw new ArgumentNullException("filePath");
180

    
181
            var fileName = FileId.ToString("N");
182
            var path = Path.Combine(filePath, fileName);
183
            if (!Directory.Exists(filePath))
184
                Directory.CreateDirectory(filePath);
185
            
186
            var json=await TaskEx.Run(()=>ToJson());
187
            await FileAsync.WriteAllText(path, json);
188
        }
189

    
190
        public static async Task<TreeHash> LoadTreeHash(string dataPath,Guid fileId)
191
        {
192
            var fileName = fileId.ToString("N");
193
            var path = Path.Combine(dataPath, fileName);
194
            
195
            var json=await FileAsync.ReadAllText(path);
196
            var treeHash = Parse(json);
197
            treeHash.FileId = fileId;
198
            return treeHash;
199
        }
200

    
201
        public static TreeHash Empty = new TreeHash(DEFAULT_HASH_ALGORITHM)
202
        {
203
            BlockSize = DEFAULT_BLOCK_SIZE,
204
            Bytes = 0
205
        };
206

    
207
        //Parse a json string and return a TreeHash
208
        //Returns an empty TreeHash if the string is null or empty
209
        public static TreeHash Parse(string json)
210
        {
211
            if (String.IsNullOrWhiteSpace(json))
212
                return Empty;            
213

    
214
            var value = JsonConvert.DeserializeObject<JObject>(json);
215
            if (value==null)
216
                throw new ArgumentException("The json parameter doesn't contain any json data","json");
217
            Contract.Assume(value!=null);
218

    
219
            var blockHash = (string) value["block_hash"];
220
            var size = value.Value<int>("block_size");
221
            var bytes = value.Value<long>("bytes");
222
            var hashes = value.Value<JArray>("hashes");
223
            var hashValues = from JToken token in hashes
224
                             select token.Value<string>().ToBytes();
225

    
226
            var treeHash = new TreeHash(blockHash)
227
                               {
228
                                   BlockSize = size,
229
                                   Hashes = hashValues.ToList(),
230
                                   Bytes = bytes
231
                               };
232
            return treeHash;
233
        }
234
    }
235
}