]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/uri.py
truncate storage index to 128 bits, since it's derived from a 128 bit AES key
[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
7
8 # the URI shall be an ascii representation of the file. It shall contain
9 # enough information to retrieve and validate the contents. It shall be
10 # expressed in a limited character set (namely [TODO]).
11
12
13 class _BaseURI:
14     def __hash__(self):
15         return hash((self.__class__, self.to_string()))
16     def __cmp__(self, them):
17         if cmp(type(self), type(them)):
18             return cmp(type(self), type(them))
19         if cmp(self.__class__, them.__class__):
20             return cmp(self.__class__, them.__class__)
21         return cmp(self.to_string(), them.to_string())
22
23 class CHKFileURI(_BaseURI):
24     implements(IURI, IFileURI)
25
26     def __init__(self, **kwargs):
27         # construct me with kwargs, since there are so many of them
28         if not kwargs:
29             return
30         keys = ("key", "uri_extension_hash",
31                 "needed_shares", "total_shares", "size")
32         for name in kwargs:
33             if name in keys:
34                 value = kwargs[name]
35                 setattr(self, name, value)
36             else:
37                 raise TypeError("CHKFileURI does not accept '%s=' argument"
38                                 % name)
39         self.storage_index = hashutil.storage_index_chk_hash(self.key)
40
41     def init_from_string(self, uri):
42         assert uri.startswith("URI:CHK:"), uri
43         d = {}
44         (header_uri, header_chk,
45          key_s, uri_extension_hash_s,
46          needed_shares_s, total_shares_s, size_s) = uri.split(":")
47         assert header_uri == "URI"
48         assert header_chk == "CHK"
49
50         self.key = idlib.a2b(key_s)
51         assert isinstance(self.key, str)
52         assert len(self.key) == 16 # AES-128
53
54         self.storage_index = hashutil.storage_index_chk_hash(self.key)
55         assert isinstance(self.storage_index, str)
56         assert len(self.storage_index) == 16 # sha256 hash truncated to 128
57
58         self.uri_extension_hash = idlib.a2b(uri_extension_hash_s)
59         assert isinstance(self.uri_extension_hash, str)
60         assert len(self.uri_extension_hash) == 32 # sha56 hash
61
62         self.needed_shares = int(needed_shares_s)
63         self.total_shares = int(total_shares_s)
64         self.size = int(size_s)
65         return self
66
67     def to_string(self):
68         assert isinstance(self.needed_shares, int)
69         assert isinstance(self.total_shares, int)
70         assert isinstance(self.size, (int,long))
71
72         return ("URI:CHK:%s:%s:%d:%d:%d" %
73                 (idlib.b2a(self.key),
74                  idlib.b2a(self.uri_extension_hash),
75                  self.needed_shares,
76                  self.total_shares,
77                  self.size))
78
79     def is_readonly(self):
80         return True
81     def is_mutable(self):
82         return False
83     def get_readonly(self):
84         return self
85
86     def get_size(self):
87         return self.size
88
89 class LiteralFileURI(_BaseURI):
90     implements(IURI, IFileURI)
91
92     def __init__(self, data=None):
93         if data is not None:
94             self.data = data
95
96     def init_from_string(self, uri):
97         assert uri.startswith("URI:LIT:")
98         data_s = uri[len("URI:LIT:"):]
99         self.data = idlib.a2b(data_s)
100         return self
101
102     def to_string(self):
103         return "URI:LIT:%s" % idlib.b2a(self.data)
104
105     def is_readonly(self):
106         return True
107     def is_mutable(self):
108         return False
109     def get_readonly(self):
110         return self
111
112     def get_size(self):
113         return len(self.data)
114
115 class DirnodeURI(_BaseURI):
116     implements(IURI, IDirnodeURI)
117
118     def __init__(self, furl=None, writekey=None):
119         if furl is not None or writekey is not None:
120             assert furl is not None
121             assert writekey is not None
122             self.furl = furl
123             self.writekey = writekey
124             self._derive_values()
125
126     def init_from_string(self, uri):
127         # URI:DIR:furl:key
128         #  but note that the furl contains colons
129         prefix = "URI:DIR:"
130         assert uri.startswith(prefix)
131         uri = uri[len(prefix):]
132         colon = uri.rindex(":")
133         self.furl = uri[:colon]
134         self.writekey = idlib.a2b(uri[colon+1:])
135         self._derive_values()
136         return self
137
138     def _derive_values(self):
139         wk, we, rk, index = \
140             hashutil.generate_dirnode_keys_from_writekey(self.writekey)
141         self.write_enabler = we
142         self.readkey = rk
143         self.storage_index = index
144
145     def to_string(self):
146         return "URI:DIR:%s:%s" % (self.furl, idlib.b2a(self.writekey))
147
148     def is_readonly(self):
149         return False
150     def is_mutable(self):
151         return True
152     def get_readonly(self):
153         return ReadOnlyDirnodeURI(self.furl, self.readkey)
154
155 class ReadOnlyDirnodeURI(_BaseURI):
156     implements(IURI, IDirnodeURI)
157
158     def __init__(self, furl=None, readkey=None):
159         if furl is not None or readkey is not None:
160             assert furl is not None
161             assert readkey is not None
162             self.furl = furl
163             self.readkey = readkey
164             self._derive_values()
165
166     def init_from_string(self, uri):
167         # URI:DIR-RO:furl:key
168         #  but note that the furl contains colons
169         prefix = "URI:DIR-RO:"
170         assert uri.startswith(prefix)
171         uri = uri[len(prefix):]
172         colon = uri.rindex(":")
173         self.furl = uri[:colon]
174         self.readkey = idlib.a2b(uri[colon+1:])
175         self._derive_values()
176         return self
177
178     def _derive_values(self):
179         wk, we, rk, index = \
180             hashutil.generate_dirnode_keys_from_readkey(self.readkey)
181         self.writekey = wk # None
182         self.write_enabler = we # None
183         self.storage_index = index
184
185     def to_string(self):
186         return "URI:DIR-RO:%s:%s" % (self.furl, idlib.b2a(self.readkey))
187
188     def is_readonly(self):
189         return True
190     def is_mutable(self):
191         return True
192     def get_readonly(self):
193         return self
194
195 def from_string(s):
196     if s.startswith("URI:CHK:"):
197         return CHKFileURI().init_from_string(s)
198     elif s.startswith("URI:LIT:"):
199         return LiteralFileURI().init_from_string(s)
200     elif s.startswith("URI:DIR:"):
201         return DirnodeURI().init_from_string(s)
202     elif s.startswith("URI:DIR-RO:"):
203         return ReadOnlyDirnodeURI().init_from_string(s)
204     else:
205         raise RuntimeError("unknown URI type: %s.." % s[:10])
206
207 registerAdapter(from_string, str, IURI)
208
209 def from_string_dirnode(s):
210     u = from_string(s)
211     assert IDirnodeURI.providedBy(u)
212     return u
213
214 registerAdapter(from_string_dirnode, str, IDirnodeURI)
215
216 def from_string_filenode(s):
217     u = from_string(s)
218     assert IFileURI.providedBy(u)
219     return u
220
221 registerAdapter(from_string_filenode, str, IFileURI)
222
223
224 def pack_extension(data):
225     pieces = []
226     for k in sorted(data.keys()):
227         value = data[k]
228         if isinstance(value, (int, long)):
229             value = "%d" % value
230         assert isinstance(value, str), k
231         assert re.match(r'^[a-zA-Z_\-]+$', k)
232         pieces.append(k + ":" + hashutil.netstring(value))
233     uri_extension = "".join(pieces)
234     return uri_extension
235
236 def unpack_extension(data):
237     d = {}
238     while data:
239         colon = data.index(":")
240         key = data[:colon]
241         data = data[colon+1:]
242
243         colon = data.index(":")
244         number = data[:colon]
245         length = int(number)
246         data = data[colon+1:]
247
248         value = data[:length]
249         assert data[length] == ","
250         data = data[length+1:]
251
252         d[key] = value
253
254     # convert certain things to numbers
255     for intkey in ("size", "segment_size", "num_segments",
256                    "needed_shares", "total_shares"):
257         if intkey in d:
258             d[intkey] = int(d[intkey])
259     return d
260
261
262 def unpack_extension_readable(data):
263     unpacked = unpack_extension(data)
264     for k in sorted(unpacked.keys()):
265         if "hash" in k:
266             unpacked[k] = idlib.b2a(unpacked[k])
267     return unpacked
268