1
from collections import Iterable, Sequence
2
from .codec import consume_length_prefix, consume_payload
3
from .exceptions import DecodingError
4
from .utils import Atomic
7
def decode_lazy(rlp, sedes=None, **sedes_kwargs):
8
"""Decode an RLP encoded object in a lazy fashion.
10
If the encoded object is a bytestring, this function acts similar to
11
:func:`rlp.decode`. If it is a list however, a :class:`LazyList` is
12
returned instead. This object will decode the string lazily, avoiding
13
both horizontal and vertical traversing as much as possible.
15
The way `sedes` is applied depends on the decoded object: If it is a string
16
`sedes` deserializes it as a whole; if it is a list, each element is
17
deserialized individually. In both cases, `sedes_kwargs` are passed on.
18
Note that, if a deserializer is used, only "horizontal" but not
19
"vertical lazyness" can be preserved.
21
:param rlp: the RLP string to decode
22
:param sedes: an object implementing a method ``deserialize(code)`` which
23
is used as described above, or ``None`` if no
24
deserialization should be performed
25
:param \*\*sedes_kwargs: additional keyword arguments that will be passed
27
:returns: either the already decoded and deserialized object (if encoded as
28
a string) or an instance of :class:`rlp.LazyList`
30
item, end = consume_item_lazy(rlp, 0)
32
raise DecodingError('RLP length prefix announced wrong length', rlp)
33
if isinstance(item, LazyList):
35
item.sedes_kwargs = sedes_kwargs
38
return sedes.deserialize(item, **sedes_kwargs)
43
def consume_item_lazy(rlp, start):
44
"""Read an item from an RLP string lazily.
46
If the length prefix announces a string, the string is read; if it
47
announces a list, a :class:`LazyList` is created.
49
:param rlp: the rlp string to read from
50
:param start: the position at which to start reading
51
:returns: a tuple ``(item, end)`` where ``item`` is the read string or a
52
:class:`LazyList` and ``end`` is the position of the first
55
t, l, s = consume_length_prefix(rlp, start)
57
#item, _ = consume_payload(rlp, s, str, l), s + l
58
return consume_payload(rlp, s, str, l)
61
return LazyList(rlp, s, s + l), s + l
64
class LazyList(Sequence):
65
"""A RLP encoded list which decodes itself when necessary.
67
Both indexing with positive indices and iterating are supported.
68
Getting the length with :func:`len` is possible as well but requires full
71
:param rlp: the rlp string in which the list is encoded
72
:param start: the position of the first payload byte of the encoded list
73
:param end: the position of the last payload byte of the encoded list
74
:param sedes: a sedes object which deserializes each element of the list,
75
or ``None`` for no deserialization
76
:param \*\*sedes_kwargs: keyword arguments which will be passed on to the
80
def __init__(self, rlp, start, end, sedes=None, **sedes_kwargs):
88
self.sedes_kwargs = sedes_kwargs
91
if self.index == self.end:
92
self._len = len(self._elements)
94
assert self.index < self.end
95
item, end = consume_item_lazy(self.rlp, self.index)
98
item = self.sedes.deserialize(item, **self.sedes_kwargs)
99
self._elements.append(item)
102
def __getitem__(self, i):
104
while len(self._elements) <= i:
106
except StopIteration:
107
assert self.index == self.end
108
raise IndexError('Index %d out of range' % i)
109
return self._elements[i]
116
except StopIteration:
117
self._len = len(self._elements)
121
def peek(rlp, index, sedes=None):
122
"""Get a specific element from an rlp encoded nested list.
124
This function uses :func:`rlp.decode_lazy` and, thus, decodes only the
125
necessary parts of the string.
129
>>> rlpdata = rlp.encode([1, 2, [3, [4, 5]]])
130
>>> rlp.peek(rlpdata, 0, rlp.sedes.big_endian_int)
132
>>> rlp.peek(rlpdata, [2, 0], rlp.sedes.big_endian_int)
135
:param rlp: the rlp string
136
:param index: the index of the element to peek at (can be a list for
138
:param sedes: a sedes used to deserialize the peeked at object, or `None`
139
if no deserialization should be performed
140
:raises: :exc:`IndexError` if `index` is invalid (out of range or too many
143
ll = decode_lazy(rlp)
144
if not isinstance(index, Iterable):
147
if isinstance(ll, Atomic):
148
raise IndexError('Too many indices given')
151
return sedes.deserialize(ll)