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 |
} |