fe9012106768cbf06f4c9f6c3835451bbe4da1af
[pithos-ms-client] / trunk%2FPithos.Network%2FTreeHash.cs
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 long DEFAULT_BLOCK_SIZE = 4*1024*1024;
60         public string BlockHash { get; set; }
61         public long 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 }