2 import itertools, struct
3 from twisted.trial import unittest
4 from twisted.internet import defer
5 from twisted.python import failure, log
6 from allmydata import mutable, uri, dirnode2
7 from allmydata.dirnode2 import split_netstring
8 from allmydata.util.hashutil import netstring, tagged_hash
9 from allmydata.encode import NotEnoughPeersError
10 from allmydata.interfaces import IURI, INewDirectoryURI, IDirnodeURI, \
14 from allmydata.Crypto.Util.number import bytes_to_long
16 class Netstring(unittest.TestCase):
18 a = netstring("hello") + netstring("world")
19 self.failUnlessEqual(split_netstring(a, 2), ("hello", "world"))
20 self.failUnlessEqual(split_netstring(a, 2, False), ("hello", "world"))
21 self.failUnlessEqual(split_netstring(a, 2, True),
22 ("hello", "world", ""))
23 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2)
24 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2, False)
27 a = netstring("hello")
28 self.failUnlessEqual(split_netstring(a, 1, True), ("hello", ""))
29 b = netstring("hello") + "extra stuff"
30 self.failUnlessEqual(split_netstring(b, 1, True),
31 ("hello", "extra stuff"))
33 def test_nested(self):
34 a = netstring("hello") + netstring("world") + "extra stuff"
35 b = netstring("a") + netstring("is") + netstring(a) + netstring(".")
36 top = split_netstring(b, 4)
37 self.failUnlessEqual(len(top), 4)
38 self.failUnlessEqual(top[0], "a")
39 self.failUnlessEqual(top[1], "is")
40 self.failUnlessEqual(top[2], a)
41 self.failUnlessEqual(top[3], ".")
42 self.failUnlessRaises(ValueError, split_netstring, a, 2)
43 self.failUnlessRaises(ValueError, split_netstring, a, 2, False)
44 bottom = split_netstring(a, 2, True)
45 self.failUnlessEqual(bottom, ("hello", "world", "extra stuff"))
47 class FakeFilenode(mutable.MutableFileNode):
48 counter = itertools.count(1)
52 def create(self, initial_contents, wait_for_numpeers=None):
53 d = mutable.MutableFileNode.create(self, initial_contents, wait_for_numpeers=None)
55 self.all_contents[self.get_uri()] = initial_contents
59 def init_from_uri(self, myuri):
60 mutable.MutableFileNode.init_from_uri(self, myuri)
62 def replace(self, newdata, wait_for_numpeers=None):
63 self.all_contents[self.get_uri()] = initial_contents
64 return defer.succeed(self)
65 def _generate_pubprivkeys(self):
66 count = self.counter.next()
67 return FakePubKey(count), FakePrivKey(count)
68 def _publish(self, initial_contents, wait_for_numpeers):
69 self.all_contents[self.get_uri()] = initial_contents
70 return defer.succeed(self)
72 def download_to_data(self):
73 if self.is_readonly():
74 assert self.all_rw_friends.has_key(self.get_uri()), (self.get_uri(), id(self.all_rw_friends))
75 return defer.succeed(self.all_contents[self.all_rw_friends[self.get_uri()]])
77 return defer.succeed(self.all_contents[self.get_uri()])
78 def replace(self, newdata, wait_for_numpeers=None):
79 self.all_contents[self.get_uri()] = newdata
80 return defer.succeed(None)
82 class FakePublish(mutable.Publish):
83 def _do_query(self, conn, peerid, peer_storage_servers, storage_index):
84 assert conn[0] == peerid
85 shares = self._peers[peerid]
86 return defer.succeed(shares)
88 def _do_testreadwrite(self, peerid, peer_storage_servers, secrets,
89 tw_vectors, read_vector):
90 # always-pass: parrot the test vectors back to them.
92 for shnum, (testv, datav, new_length) in tw_vectors.items():
93 for (offset, length, op, specimen) in testv:
94 assert op in ("le", "eq", "ge")
95 readv[shnum] = [ specimen
96 for (offset, length, op, specimen)
98 answer = (True, readv)
99 return defer.succeed(answer)
102 class FakeNewDirectoryNode(dirnode2.NewDirectoryNode):
103 filenode_class = FakeFilenode
105 class FakeIntroducerClient:
106 def when_enough_peers(self, numpeers):
107 return defer.succeed(None)
110 def __init__(self, num_peers=10):
111 self._num_peers = num_peers
112 self._peerids = [tagged_hash("peerid", "%d" % i)[:20]
113 for i in range(self._num_peers)]
114 self.introducer_client = FakeIntroducerClient()
118 def get_renewal_secret(self):
119 return "I hereby permit you to renew my files"
120 def get_cancel_secret(self):
121 return "I hereby permit you to cancel my leases"
123 def create_empty_dirnode(self, wait_for_numpeers):
124 n = FakeNewDirectoryNode(self)
125 d = n.create(wait_for_numpeers=wait_for_numpeers)
126 d.addCallback(lambda res: n)
129 def create_dirnode_from_uri(self, u):
130 return FakeNewDirectoryNode(self).init_from_uri(u)
132 def create_mutable_file(self, contents="", wait_for_numpeers=None):
133 n = FakeFilenode(self)
134 d = n.create(contents, wait_for_numpeers=wait_for_numpeers)
135 d.addCallback(lambda res: n)
138 def create_node_from_uri(self, u):
140 if INewDirectoryURI.providedBy(u):
141 return self.create_dirnode_from_uri(u)
142 assert IMutableFileURI.providedBy(u)
143 res = FakeFilenode(self).init_from_uri(u)
146 def get_permuted_peers(self, key, include_myself=True):
148 @return: list of (permuted-peerid, peerid, connection,)
150 peers_and_connections = [(pid, (pid,)) for pid in self._peerids]
152 for peerid, connection in peers_and_connections:
153 assert isinstance(peerid, str)
154 permuted = bytes_to_long(sha.new(key + peerid).digest())
155 results.append((permuted, peerid, connection))
159 class Filenode(unittest.TestCase):
161 self.client = FakeClient()
163 def test_create(self):
164 d = self.client.create_mutable_file(wait_for_numpeers=1)
166 d = n.replace("contents 1")
167 d.addCallback(lambda res: self.failUnlessIdentical(res, None))
168 d.addCallback(lambda res: n.download_to_data())
169 d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
170 d.addCallback(lambda res: n.replace("contents 2"))
171 d.addCallback(lambda res: n.download_to_data())
172 d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
174 d.addCallback(_created)
177 def test_create_with_initial_contents(self):
178 d = self.client.create_mutable_file("contents 1")
180 d = n.download_to_data()
181 d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
182 d.addCallback(lambda res: n.replace("contents 2"))
183 d.addCallback(lambda res: n.download_to_data())
184 d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
186 d.addCallback(_created)
190 class Publish(unittest.TestCase):
191 def test_encrypt(self):
194 # .create usually returns a Deferred, but we happen to know it's
196 CONTENTS = "some initial contents"
197 fn.create(CONTENTS, wait_for_numpeers=1)
198 p = mutable.Publish(fn)
200 d = defer.maybeDeferred(p._encrypt_and_encode, target_info,
201 CONTENTS, "READKEY", "IV"*8, 3, 10)
202 def _done( ((shares, share_ids),
203 required_shares, total_shares,
204 segsize, data_length, target_info2) ):
205 self.failUnlessEqual(len(shares), 10)
207 self.failUnless(isinstance(sh, str))
208 self.failUnlessEqual(len(sh), 7)
209 self.failUnlessEqual(len(share_ids), 10)
210 self.failUnlessEqual(required_shares, 3)
211 self.failUnlessEqual(total_shares, 10)
212 self.failUnlessEqual(segsize, 21)
213 self.failUnlessEqual(data_length, len(CONTENTS))
214 self.failUnlessIdentical(target_info, target_info2)
218 def test_generate(self):
221 # .create usually returns a Deferred, but we happen to know it's
223 CONTENTS = "some initial contents"
224 fn.create(CONTENTS, wait_for_numpeers=1)
225 p = mutable.Publish(fn)
226 r = mutable.Retrieve(fn)
227 # make some fake shares
228 shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
230 p._privkey = FakePrivKey(0)
231 p._encprivkey = "encprivkey"
232 p._pubkey = FakePubKey(0)
233 d = defer.maybeDeferred(p._generate_shares,
241 def _done( (seqnum, root_hash, final_shares, target_info2) ):
242 self.failUnlessEqual(seqnum, 3)
243 self.failUnlessEqual(len(root_hash), 32)
244 self.failUnless(isinstance(final_shares, dict))
245 self.failUnlessEqual(len(final_shares), 10)
246 self.failUnlessEqual(sorted(final_shares.keys()), range(10))
247 for i,sh in final_shares.items():
248 self.failUnless(isinstance(sh, str))
249 self.failUnlessEqual(len(sh), 381)
250 # feed the share through the unpacker as a sanity-check
251 pieces = mutable.unpack_share(sh)
252 (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
253 pubkey, signature, share_hash_chain, block_hash_tree,
254 share_data, enc_privkey) = pieces
255 self.failUnlessEqual(u_seqnum, 3)
256 self.failUnlessEqual(u_root_hash, root_hash)
257 self.failUnlessEqual(k, 3)
258 self.failUnlessEqual(N, 10)
259 self.failUnlessEqual(segsize, 21)
260 self.failUnlessEqual(datalen, len(CONTENTS))
261 self.failUnlessEqual(pubkey, FakePubKey(0).serialize())
262 sig_material = struct.pack(">BQ32s16s BBQQ",
263 0, seqnum, root_hash, IV,
264 k, N, segsize, datalen)
265 self.failUnlessEqual(signature,
266 FakePrivKey(0).sign(sig_material))
267 self.failUnless(isinstance(share_hash_chain, dict))
268 self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
269 for shnum,share_hash in share_hash_chain.items():
270 self.failUnless(isinstance(shnum, int))
271 self.failUnless(isinstance(share_hash, str))
272 self.failUnlessEqual(len(share_hash), 32)
273 self.failUnless(isinstance(block_hash_tree, list))
274 self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
275 self.failUnlessEqual(IV, "IV"*8)
276 self.failUnlessEqual(len(share_data), len("%07d" % 1))
277 self.failUnlessEqual(enc_privkey, "encprivkey")
278 self.failUnlessIdentical(target_info, target_info2)
282 def setup_for_sharemap(self, num_peers):
283 c = FakeClient(num_peers)
285 # .create usually returns a Deferred, but we happen to know it's
287 CONTENTS = "some initial contents"
290 p._storage_index = "\x00"*32
291 #r = mutable.Retrieve(fn)
293 for peerid in c._peerids:
294 p._peers[peerid] = {}
297 def shouldFail(self, expected_failure, which, call, *args, **kwargs):
298 substring = kwargs.pop("substring", None)
299 d = defer.maybeDeferred(call, *args, **kwargs)
301 if isinstance(res, failure.Failure):
302 res.trap(expected_failure)
304 self.failUnless(substring in str(res),
305 "substring '%s' not in '%s'"
306 % (substring, str(res)))
308 self.fail("%s was supposed to raise %s, not get '%s'" %
309 (which, expected_failure, res))
313 def test_sharemap_20newpeers(self):
314 c, p = self.setup_for_sharemap(20)
317 d = p._query_peers(total_shares)
318 def _done(target_info):
319 (target_map, shares_per_peer, peer_storage_servers) = target_info
321 for shnum in target_map:
322 for (peerid, old_seqnum, old_R) in target_map[shnum]:
323 #print "shnum[%d]: send to %s [oldseqnum=%s]" % \
324 # (shnum, idlib.b2a(peerid), old_seqnum)
325 if peerid not in shares_per_peer:
326 shares_per_peer[peerid] = 1
328 shares_per_peer[peerid] += 1
329 # verify that we're sending only one share per peer
330 for peerid, count in shares_per_peer.items():
331 self.failUnlessEqual(count, 1)
335 def test_sharemap_3newpeers(self):
336 c, p = self.setup_for_sharemap(3)
339 d = p._query_peers(total_shares)
340 def _done(target_info):
341 (target_map, shares_per_peer, peer_storage_servers) = target_info
343 for shnum in target_map:
344 for (peerid, old_seqnum, old_R) in target_map[shnum]:
345 if peerid not in shares_per_peer:
346 shares_per_peer[peerid] = 1
348 shares_per_peer[peerid] += 1
349 # verify that we're sending 3 or 4 shares per peer
350 for peerid, count in shares_per_peer.items():
351 self.failUnless(count in (3,4), count)
355 def test_sharemap_nopeers(self):
356 c, p = self.setup_for_sharemap(0)
359 d = self.shouldFail(NotEnoughPeersError, "test_sharemap_nopeers",
360 p._query_peers, total_shares)
363 def test_write(self):
365 c, p = self.setup_for_sharemap(20)
366 p._privkey = FakePrivKey(0)
367 p._encprivkey = "encprivkey"
368 p._pubkey = FakePubKey(0)
369 # make some fake shares
370 CONTENTS = "some initial contents"
371 shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
372 d = defer.maybeDeferred(p._query_peers, total_shares)
374 d.addCallback(lambda target_info:
375 p._generate_shares( (shares_and_ids,
382 d.addCallback(p._send_shares, IV)
383 def _done((surprised, dispatch_map)):
384 self.failIf(surprised, "surprised!")
388 def setup_for_publish(self, num_peers):
389 c = FakeClient(num_peers)
391 # .create usually returns a Deferred, but we happen to know it's
396 for peerid in c._peerids:
397 p._peers[peerid] = {}
400 def test_publish(self):
401 c, fn, p = self.setup_for_publish(20)
402 # make sure the length of our contents string is not a multiple of k,
403 # to exercise the padding code.
404 d = p.publish("New contents of the mutable filenode.")
406 # TODO: examine peers and check on their shares
413 def __init__(self, count):
416 return "PUBKEY-%d" % self.count
417 def verify(self, msg, signature):
421 def __init__(self, count):
424 return "PRIVKEY-%d" % self.count
425 def sign(self, data):
426 return "SIGN(%s)" % data
428 class Dirnode(unittest.TestCase):
430 self.client = FakeClient()
432 def test_create(self):
433 self.expected_manifest = []
435 d = self.client.create_empty_dirnode(wait_for_numpeers=1)
437 self.failUnless(n.is_mutable())
440 self.failUnless(u.startswith("URI:DIR2:"), u)
441 u_ro = n.get_readonly_uri()
442 self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
443 u_v = n.get_verifier()
444 self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
445 self.expected_manifest.append(u_v)
448 d.addCallback(lambda res: self.failUnlessEqual(res, {}))
449 d.addCallback(lambda res: n.has_child("missing"))
450 d.addCallback(lambda res: self.failIf(res))
451 fake_file_uri = uri.WriteableSSKFileURI("a"*16,"b"*32)
452 ffu_v = fake_file_uri.get_verifier().to_string()
453 self.expected_manifest.append(ffu_v)
454 d.addCallback(lambda res: n.set_uri("child", fake_file_uri))
456 d.addCallback(lambda res: n.create_empty_directory("subdir", wait_for_numpeers=1))
457 def _created(subdir):
458 self.failUnless(isinstance(subdir, FakeNewDirectoryNode))
460 new_v = subdir.get_verifier()
461 self.expected_manifest.append(new_v)
462 d.addCallback(_created)
464 d.addCallback(lambda res: n.list())
465 d.addCallback(lambda children:
466 self.failUnlessEqual(sorted(children.keys()),
467 sorted(["child", "subdir"])))
469 d.addCallback(lambda res: n.build_manifest())
470 def _check_manifest(manifest):
471 self.failUnlessEqual(sorted(manifest),
472 sorted(self.expected_manifest))
473 d.addCallback(_check_manifest)
475 def _add_subsubdir(res):
476 return self.subdir.create_empty_directory("subsubdir", wait_for_numpeers=1)
477 d.addCallback(_add_subsubdir)
478 d.addCallback(lambda res: n.get_child_at_path("subdir/subsubdir"))
479 d.addCallback(lambda subsubdir:
480 self.failUnless(isinstance(subsubdir,
481 FakeNewDirectoryNode)))
482 d.addCallback(lambda res: n.get_child_at_path(""))
483 d.addCallback(lambda res: self.failUnlessEqual(res.get_uri(),
486 d.addCallback(lambda res: n.get_metadata_for("child"))
487 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
489 d.addCallback(lambda res: n.delete("subdir"))
490 d.addCallback(lambda old_child:
491 self.failUnlessEqual(old_child.get_uri(),
492 self.subdir.get_uri()))
494 d.addCallback(lambda res: n.list())
495 d.addCallback(lambda children:
496 self.failUnlessEqual(sorted(children.keys()),