]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/uri.py
decentralized directories: integration and testing
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / uri.py
1
2 import re
3 from zope.interface import implements
4 from twisted.python.components import registerAdapter
5 from allmydata.util import idlib, hashutil
6 from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IVerifierURI, \
7      IMutableFileURI, INewDirectoryURI, IReadonlyNewDirectoryURI
8
9 # the URI shall be an ascii representation of the file. It shall contain
10 # enough information to retrieve and validate the contents. It shall be
11 # expressed in a limited character set (namely [TODO]).
12
13
14 class _BaseURI:
15     def __hash__(self):
16         return hash((self.__class__, self.to_string()))
17     def __cmp__(self, them):
18         if cmp(type(self), type(them)):
19             return cmp(type(self), type(them))
20         if cmp(self.__class__, them.__class__):
21             return cmp(self.__class__, them.__class__)
22         return cmp(self.to_string(), them.to_string())
23
24 class CHKFileURI(_BaseURI):
25     implements(IURI, IFileURI)
26
27     def __init__(self, **kwargs):
28         # construct me with kwargs, since there are so many of them
29         if not kwargs:
30             return
31         keys = ("key", "uri_extension_hash",
32                 "needed_shares", "total_shares", "size")
33         for name in kwargs:
34             if name in keys:
35                 value = kwargs[name]
36                 setattr(self, name, value)
37             else:
38                 raise TypeError("CHKFileURI does not accept '%s=' argument"
39                                 % name)
40         self.storage_index = hashutil.storage_index_chk_hash(self.key)
41
42     def init_from_string(self, uri):
43         assert uri.startswith("URI:CHK:"), uri
44         d = {}
45         (header_uri, header_chk,
46          key_s, uri_extension_hash_s,
47          needed_shares_s, total_shares_s, size_s) = uri.split(":")
48         assert header_uri == "URI"
49         assert header_chk == "CHK"
50
51         self.key = idlib.a2b(key_s)
52         assert isinstance(self.key, str)
53         assert len(self.key) == 16 # AES-128
54
55         self.storage_index = hashutil.storage_index_chk_hash(self.key)
56         assert isinstance(self.storage_index, str)
57         assert len(self.storage_index) == 16 # sha256 hash truncated to 128
58
59         self.uri_extension_hash = idlib.a2b(uri_extension_hash_s)
60         assert isinstance(self.uri_extension_hash, str)
61         assert len(self.uri_extension_hash) == 32 # sha56 hash
62
63         self.needed_shares = int(needed_shares_s)
64         self.total_shares = int(total_shares_s)
65         self.size = int(size_s)
66         return self
67
68     def to_string(self):
69         assert isinstance(self.needed_shares, int)
70         assert isinstance(self.total_shares, int)
71         assert isinstance(self.size, (int,long))
72
73         return ("URI:CHK:%s:%s:%d:%d:%d" %
74                 (idlib.b2a(self.key),
75                  idlib.b2a(self.uri_extension_hash),
76                  self.needed_shares,
77                  self.total_shares,
78                  self.size))
79
80     def is_readonly(self):
81         return True
82     def is_mutable(self):
83         return False
84     def get_readonly(self):
85         return self
86
87     def get_size(self):
88         return self.size
89
90     def get_verifier(self):
91         return CHKFileVerifierURI(storage_index=self.storage_index,
92                                   uri_extension_hash=self.uri_extension_hash,
93                                   needed_shares=self.needed_shares,
94                                   total_shares=self.total_shares,
95                                   size=self.size)
96
97 class CHKFileVerifierURI(_BaseURI):
98     implements(IVerifierURI)
99
100     def __init__(self, **kwargs):
101         # construct me with kwargs, since there are so many of them
102         if not kwargs:
103             return
104         keys = ("storage_index", "uri_extension_hash",
105                 "needed_shares", "total_shares", "size")
106         for name in kwargs:
107             if name in keys:
108                 value = kwargs[name]
109                 setattr(self, name, value)
110             else:
111                 raise TypeError("CHKFileVerifierURI does not accept "
112                                 "'%s=' argument"
113                                 % name)
114
115     def init_from_string(self, uri):
116         assert uri.startswith("URI:CHK-Verifier:"), uri
117         d = {}
118         (header_uri, header_chk,
119          storage_index_s, uri_extension_hash_s,
120          needed_shares_s, total_shares_s, size_s) = uri.split(":")
121         assert header_uri == "URI"
122         assert header_chk == "CHK-Verifier"
123
124         self.storage_index = idlib.a2b(storage_index_s)
125         assert isinstance(self.storage_index, str)
126         assert len(self.storage_index) == 16 # sha256 hash truncated to 128
127
128         self.uri_extension_hash = idlib.a2b(uri_extension_hash_s)
129         assert isinstance(self.uri_extension_hash, str)
130         assert len(self.uri_extension_hash) == 32 # sha56 hash
131
132         self.needed_shares = int(needed_shares_s)
133         self.total_shares = int(total_shares_s)
134         self.size = int(size_s)
135         return self
136
137     def to_string(self):
138         assert isinstance(self.needed_shares, int)
139         assert isinstance(self.total_shares, int)
140         assert isinstance(self.size, (int,long))
141
142         return ("URI:CHK-Verifier:%s:%s:%d:%d:%d" %
143                 (idlib.b2a(self.storage_index),
144                  idlib.b2a(self.uri_extension_hash),
145                  self.needed_shares,
146                  self.total_shares,
147                  self.size))
148
149
150 class LiteralFileURI(_BaseURI):
151     implements(IURI, IFileURI)
152
153     def __init__(self, data=None):
154         if data is not None:
155             self.data = data
156
157     def init_from_string(self, uri):
158         assert uri.startswith("URI:LIT:")
159         data_s = uri[len("URI:LIT:"):]
160         self.data = idlib.a2b(data_s)
161         return self
162
163     def to_string(self):
164         return "URI:LIT:%s" % idlib.b2a(self.data)
165
166     def is_readonly(self):
167         return True
168     def is_mutable(self):
169         return False
170     def get_readonly(self):
171         return self
172
173     def get_verifier(self):
174         # LIT files need no verification, all the data is present in the URI
175         return None
176
177     def get_size(self):
178         return len(self.data)
179
180 class WriteableSSKFileURI(_BaseURI):
181     implements(IURI, IMutableFileURI)
182
183     def __init__(self, *args, **kwargs):
184         if not args and not kwargs:
185             return
186         self.populate(*args, **kwargs)
187
188     def populate(self, writekey, fingerprint):
189         self.writekey = writekey
190         self.readkey = hashutil.ssk_readkey_hash(writekey)
191         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
192         self.fingerprint = fingerprint
193
194     def init_from_string(self, uri):
195         assert uri.startswith("URI:SSK:"), uri
196         (header_uri, header_ssk, writekey_s, fingerprint_s) = uri.split(":")
197         self.populate(idlib.a2b(writekey_s), idlib.a2b(fingerprint_s))
198         return self
199
200     def to_string(self):
201         assert isinstance(self.writekey, str)
202         assert isinstance(self.fingerprint, str)
203         return "URI:SSK:%s:%s" % (idlib.b2a(self.writekey),
204                                   idlib.b2a(self.fingerprint))
205
206     def __repr__(self):
207         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
208
209     def abbrev(self):
210         return idlib.b2a(self.writekey[:5])
211
212     def is_readonly(self):
213         return False
214     def is_mutable(self):
215         return True
216     def get_readonly(self):
217         return ReadonlySSKFileURI(self.readkey, self.fingerprint)
218     def get_verifier(self):
219         return SSKVerifierURI(self.storage_index, self.fingerprint)
220
221 class ReadonlySSKFileURI(_BaseURI):
222     implements(IURI, IMutableFileURI)
223
224     def __init__(self, *args, **kwargs):
225         if not args and not kwargs:
226             return
227         self.populate(*args, **kwargs)
228
229     def populate(self, readkey, fingerprint):
230         self.readkey = readkey
231         self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
232         self.fingerprint = fingerprint
233
234     def init_from_string(self, uri):
235         assert uri.startswith("URI:SSK-RO:"), uri
236         (header_uri, header_ssk, readkey_s, fingerprint_s) = uri.split(":")
237         self.populate(idlib.a2b(readkey_s), idlib.a2b(fingerprint_s))
238         return self
239
240     def to_string(self):
241         assert isinstance(self.readkey, str)
242         assert isinstance(self.fingerprint, str)
243         return "URI:SSK-RO:%s:%s" % (idlib.b2a(self.readkey),
244                                      idlib.b2a(self.fingerprint))
245
246     def __repr__(self):
247         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
248
249     def abbrev(self):
250         return idlib.b2a(self.readkey[:5])
251
252     def is_readonly(self):
253         return True
254     def is_mutable(self):
255         return True
256     def get_readonly(self):
257         return self
258     def get_verifier(self):
259         return SSKVerifierURI(self.storage_index, self.fingerprint)
260
261 class SSKVerifierURI(_BaseURI):
262     implements(IVerifierURI)
263
264     def __init__(self, *args, **kwargs):
265         if not args and not kwargs:
266             return
267         self.populate(*args, **kwargs)
268
269     def populate(self, storage_index, fingerprint):
270         self.storage_index = storage_index
271         self.fingerprint = fingerprint
272
273     def init_from_string(self, uri):
274         assert uri.startswith("URI:SSK-Verifier:"), uri
275         (header_uri, header_ssk,
276          storage_index_s, fingerprint_s) = uri.split(":")
277         self.populate(idlib.a2b(storage_index_s), idlib.a2b(fingerprint_s))
278         return self
279
280     def to_string(self):
281         assert isinstance(self.storage_index, str)
282         assert isinstance(self.fingerprint, str)
283         return "URI:SSK-Verifier:%s:%s" % (idlib.b2a(self.storage_index),
284                                            idlib.b2a(self.fingerprint))
285
286 class _NewDirectoryBaseURI(_BaseURI):
287     implements(IURI, IDirnodeURI)
288     def __init__(self, filenode_uri=None):
289         self._filenode_uri = filenode_uri
290
291     def __repr__(self):
292         return "<%s %s>" % (self.__class__.__name__, self.abbrev())
293
294     def abbrev(self):
295         return self._filenode_uri.to_string().split(':')[2][:5]
296
297     def get_filenode_uri(self):
298         return self._filenode_uri
299
300     def is_mutable(self):
301         return True
302
303     def get_verifier(self):
304         return NewDirectoryURIVerifier(self._filenode_uri.get_verifier())
305
306 class NewDirectoryURI(_NewDirectoryBaseURI):
307     implements(INewDirectoryURI)
308     def __init__(self, filenode_uri=None):
309         if filenode_uri:
310             assert not filenode_uri.is_readonly()
311         _NewDirectoryBaseURI.__init__(self, filenode_uri)
312
313     def init_from_string(self, uri):
314         assert uri.startswith("URI:DIR2:")
315         (header_uri, header_dir2, bits) = uri.split(":", 2)
316         fn = WriteableSSKFileURI()
317         fn.init_from_string("URI:SSK:" + bits)
318         self._filenode_uri = fn
319         return self
320
321     def to_string(self):
322         assert isinstance(self._filenode_uri, WriteableSSKFileURI)
323         fn_u = self._filenode_uri.to_string()
324         (header_uri, header_ssk, bits) = fn_u.split(":", 2)
325         return "URI:DIR2:" + bits
326
327     def is_readonly(self):
328         return False
329
330     def get_readonly(self):
331         return ReadonlyNewDirectoryURI(self._filenode_uri.get_readonly())
332
333 class ReadonlyNewDirectoryURI(_NewDirectoryBaseURI):
334     implements(IReadonlyNewDirectoryURI)
335     def __init__(self, filenode_uri=None):
336         if filenode_uri:
337             assert filenode_uri.is_readonly()
338         _NewDirectoryBaseURI.__init__(self, filenode_uri)
339
340     def init_from_string(self, uri):
341         assert uri.startswith("URI:DIR2-RO:")
342         (header_uri, header_dir2, bits) = uri.split(":", 2)
343         fn = ReadonlySSKFileURI()
344         fn.init_from_string("URI:SSK-RO:" + bits)
345         self._filenode_uri = fn
346         return self
347
348     def to_string(self):
349         assert isinstance(self._filenode_uri, ReadonlySSKFileURI)
350         fn_u = self._filenode_uri.to_string()
351         (header_uri, header_ssk, bits) = fn_u.split(":", 2)
352         return "URI:DIR2-RO:" + bits
353
354     def is_readonly(self):
355         return True
356
357     def get_readonly(self):
358         return self
359
360 class NewDirectoryURIVerifier(_BaseURI):
361     implements(IVerifierURI)
362
363     def __init__(self, filenode_uri=None):
364         if filenode_uri:
365             filenode_uri = IVerifierURI(filenode_uri)
366         self._filenode_uri = filenode_uri
367
368     def init_from_string(self, uri):
369         assert uri.startswith("URI:DIR2-Verifier:")
370         (header_uri, header_dir2, bits) = uri.split(":", 2)
371         fn = SSKVerifierURI()
372         fn.init_from_string("URI:SSK-Verifier:" + bits)
373         self._filenode_uri = fn
374         return self
375
376     def to_string(self):
377         assert isinstance(self._filenode_uri, SSKVerifierURI)
378         fn_u = self._filenode_uri.to_string()
379         (header_uri, header_ssk, bits) = fn_u.split(":", 2)
380         return "URI:DIR2-Verifier:" + bits
381
382     def get_filenode_uri(self):
383         return self._filenode_uri
384
385
386
387 class DirnodeURI(_BaseURI):
388     implements(IURI, IDirnodeURI)
389
390     def __init__(self, furl=None, writekey=None):
391         if furl is not None or writekey is not None:
392             assert furl is not None
393             assert writekey is not None
394             self.furl = furl
395             self.writekey = writekey
396             self._derive_values()
397
398     def init_from_string(self, uri):
399         # URI:DIR:furl:key
400         #  but note that the furl contains colons
401         prefix = "URI:DIR:"
402         assert uri.startswith(prefix)
403         uri = uri[len(prefix):]
404         colon = uri.rindex(":")
405         self.furl = uri[:colon]
406         self.writekey = idlib.a2b(uri[colon+1:])
407         self._derive_values()
408         return self
409
410     def _derive_values(self):
411         wk, we, rk, index = \
412             hashutil.generate_dirnode_keys_from_writekey(self.writekey)
413         self.write_enabler = we
414         self.readkey = rk
415         self.storage_index = index
416
417     def to_string(self):
418         return "URI:DIR:%s:%s" % (self.furl, idlib.b2a(self.writekey))
419
420     def is_readonly(self):
421         return False
422     def is_mutable(self):
423         return True
424     def get_readonly(self):
425         return ReadOnlyDirnodeURI(self.furl, self.readkey)
426     def get_verifier(self):
427         return DirnodeVerifierURI(self.furl, self.storage_index)
428
429 class ReadOnlyDirnodeURI(_BaseURI):
430     implements(IURI, IDirnodeURI)
431
432     def __init__(self, furl=None, readkey=None):
433         if furl is not None or readkey is not None:
434             assert furl is not None
435             assert readkey is not None
436             self.furl = furl
437             self.readkey = readkey
438             self._derive_values()
439
440     def init_from_string(self, uri):
441         # URI:DIR-RO:furl:key
442         #  but note that the furl contains colons
443         prefix = "URI:DIR-RO:"
444         assert uri.startswith(prefix)
445         uri = uri[len(prefix):]
446         colon = uri.rindex(":")
447         self.furl = uri[:colon]
448         self.readkey = idlib.a2b(uri[colon+1:])
449         self._derive_values()
450         return self
451
452     def _derive_values(self):
453         wk, we, rk, index = \
454             hashutil.generate_dirnode_keys_from_readkey(self.readkey)
455         self.writekey = wk # None
456         self.write_enabler = we # None
457         self.storage_index = index
458
459     def to_string(self):
460         return "URI:DIR-RO:%s:%s" % (self.furl, idlib.b2a(self.readkey))
461
462     def is_readonly(self):
463         return True
464     def is_mutable(self):
465         return True
466     def get_readonly(self):
467         return self
468     def get_verifier(self):
469         return DirnodeVerifierURI(self.furl, self.storage_index)
470
471 class DirnodeVerifierURI(_BaseURI):
472     implements(IVerifierURI)
473
474     def __init__(self, furl=None, storage_index=None):
475         if furl is not None or storage_index is not None:
476             assert furl is not None
477             assert storage_index is not None
478             self.furl = furl
479             self.storage_index = storage_index
480
481     def init_from_string(self, uri):
482         # URI:DIR-Verifier:furl:storageindex
483         #  but note that the furl contains colons
484         prefix = "URI:DIR-Verifier:"
485         assert uri.startswith(prefix)
486         uri = uri[len(prefix):]
487         colon = uri.rindex(":")
488         self.furl = uri[:colon]
489         self.storage_index = idlib.a2b(uri[colon+1:])
490         return self
491
492     def to_string(self):
493         return "URI:DIR-Verifier:%s:%s" % (self.furl,
494                                            idlib.b2a(self.storage_index))
495
496
497
498 def from_string(s):
499     if s.startswith("URI:CHK:"):
500         return CHKFileURI().init_from_string(s)
501     elif s.startswith("URI:CHK-Verifier:"):
502         return CHKFileVerifierURI().init_from_string(s)
503     elif s.startswith("URI:LIT:"):
504         return LiteralFileURI().init_from_string(s)
505     elif s.startswith("URI:DIR:"):
506         return DirnodeURI().init_from_string(s)
507     elif s.startswith("URI:DIR-RO:"):
508         return ReadOnlyDirnodeURI().init_from_string(s)
509     elif s.startswith("URI:DIR-Verifier:"):
510         return DirnodeVerifierURI().init_from_string(s)
511     elif s.startswith("URI:SSK:"):
512         return WriteableSSKFileURI().init_from_string(s)
513     elif s.startswith("URI:SSK-RO:"):
514         return ReadonlySSKFileURI().init_from_string(s)
515     elif s.startswith("URI:SSK-Verifier:"):
516         return SSKVerifierURI().init_from_string(s)
517     elif s.startswith("URI:DIR2:"):
518         return NewDirectoryURI().init_from_string(s)
519     elif s.startswith("URI:DIR2-RO:"):
520         return ReadonlyNewDirectoryURI().init_from_string(s)
521     elif s.startswith("URI:DIR2-Verifier:"):
522         return NewDirectoryURIVerifier().init_from_string(s)
523     else:
524         raise TypeError("unknown URI type: %s.." % s[:12])
525
526 registerAdapter(from_string, str, IURI)
527
528 def from_string_dirnode(s):
529     u = from_string(s)
530     assert IDirnodeURI.providedBy(u)
531     return u
532
533 registerAdapter(from_string_dirnode, str, IDirnodeURI)
534
535 def is_string_newdirnode_rw(s):
536     if not s.startswith("URI:DIR2:"):
537         return False
538     try:
539         (header_uri, header_dir2, writekey_s, fingerprint_s) = s.split(":", 2)
540     except ValueError:
541         return False
542     return idlib.could_be_base32_encoded(writekey_s) and idlib.could_be_base32_encoded(fingerprint_s)
543
544 def from_string_filenode(s):
545     u = from_string(s)
546     assert IFileURI.providedBy(u)
547     return u
548
549 registerAdapter(from_string_filenode, str, IFileURI)
550
551 def from_string_mutable_filenode(s):
552     u = from_string(s)
553     assert IMutableFileURI.providedBy(u)
554     return u
555 registerAdapter(from_string_mutable_filenode, str, IMutableFileURI)
556
557 def from_string_verifier(s):
558     u = from_string(s)
559     assert IVerifierURI.providedBy(u)
560     return u
561 registerAdapter(from_string_verifier, str, IVerifierURI)
562
563
564 def pack_extension(data):
565     pieces = []
566     for k in sorted(data.keys()):
567         value = data[k]
568         if isinstance(value, (int, long)):
569             value = "%d" % value
570         assert isinstance(value, str), k
571         assert re.match(r'^[a-zA-Z_\-]+$', k)
572         pieces.append(k + ":" + hashutil.netstring(value))
573     uri_extension = "".join(pieces)
574     return uri_extension
575
576 def unpack_extension(data):
577     d = {}
578     while data:
579         colon = data.index(":")
580         key = data[:colon]
581         data = data[colon+1:]
582
583         colon = data.index(":")
584         number = data[:colon]
585         length = int(number)
586         data = data[colon+1:]
587
588         value = data[:length]
589         assert data[length] == ","
590         data = data[length+1:]
591
592         d[key] = value
593
594     # convert certain things to numbers
595     for intkey in ("size", "segment_size", "num_segments",
596                    "needed_shares", "total_shares"):
597         if intkey in d:
598             d[intkey] = int(d[intkey])
599     return d
600
601
602 def unpack_extension_readable(data):
603     unpacked = unpack_extension(data)
604     for k in sorted(unpacked.keys()):
605         if "hash" in k:
606             unpacked[k] = idlib.b2a(unpacked[k])
607     return unpacked
608