def decode(rlp, sedes=None, strict=True, recursive_cache=False, **kwargs): """Decode an RLP encoded object. If the deserialized result `obj` has an attribute :attr:`_cached_rlp` (e.g. if `sedes` is a subclass of :class:`rlp.Serializable`) it will be set to `rlp`, which will improve performance on subsequent :func:`rlp.encode` calls. Bear in mind however that `obj` needs to make sure that this value is updated whenever one of its fields changes or prevent such changes entirely (:class:`rlp.sedes.Serializable` does the latter). :param sedes: an object implementing a function ``deserialize(code)`` which will be applied after decoding, or ``None`` if no deserialization should be performed :param \*\*kwargs: additional keyword arguments that will be passed to the deserializer :param strict: if false inputs that are longer than necessary don't cause an exception :returns: the decoded and maybe deserialized Python object :raises: :exc:`rlp.DecodingError` if the input string does not end after the root item and `strict` is true :raises: :exc:`rlp.DeserializationError` if the deserialization fails """ if not is_bytes(rlp): raise DecodingError('Can only decode RLP bytes, got type %s' % type(rlp).__name__, rlp) try: item, per_item_rlp, end = consume_item(rlp, 0) except IndexError: raise DecodingError('RLP string too short', rlp) if end != len(rlp) and strict: msg = 'RLP string ends with {} superfluous bytes'.format(len(rlp) - end) raise DecodingError(msg, rlp) if sedes: obj = sedes.deserialize(item, **kwargs) if is_sequence(obj) or hasattr(obj, '_cached_rlp'): _apply_rlp_cache(obj, per_item_rlp, recursive_cache) return obj else: return item
def consume_length_prefix(rlp, start): """Read a length prefix from an RLP string. :param rlp: the rlp byte string to read from :param start: the position at which to start reading :returns: a tuple ``(type, length, end)``, where ``type`` is either ``str`` or ``list`` depending on the type of the following payload, ``length`` is the length of the payload in bytes, and ``end`` is the position of the first payload byte in the rlp string """ b0 = rlp[start] if b0 < 128: # single byte return (bytes, 1, start) elif b0 < SHORT_STRING: # short string if b0 - 128 == 1 and rlp[start + 1] < 128: raise DecodingError('Encoded as short string although single byte was possible', rlp) return (bytes, b0 - 128, start + 1) elif b0 < 192: # long string ll = b0 - 183 # - (128 + 56 - 1) if rlp[start + 1:start + 2] == b'\x00': raise DecodingError('Length starts with zero bytes', rlp) l = big_endian_to_int(rlp[start + 1:start + 1 + ll]) if l < 56: raise DecodingError('Long string prefix used for short string', rlp) return (bytes, l, start + 1 + ll) elif b0 < 192 + 56: # short list return (list, b0 - 192, start + 1) else: # long list ll = b0 - 192 - 56 + 1 if rlp[start + 1:start + 2] == b'\x00': raise DecodingError('Length starts with zero bytes', rlp) l = big_endian_to_int(rlp[start + 1:start + 1 + ll]) if l < 56: raise DecodingError('Long list prefix used for short list', rlp) return (list, l, start + 1 + ll)
def decode_raw(item, strict, _): try: result, per_item_rlp, end = consume_item(item, 0) except IndexError: raise DecodingError('RLP string too short', item) if end != len(item) and strict: msg = 'RLP string ends with {} superfluous bytes'.format( len(item) - end) raise DecodingError(msg, item) return result, per_item_rlp
def consume_payload(rlp, start, type_, length): """Read the payload of an item from an RLP string. :param rlp: the rlp string to read from :param type_: the type of the payload (``bytes`` or ``list``) :param start: the position at which to start reading :param length: the length of the payload in bytes :returns: a tuple ``(item, end)``, where ``item`` is the read item and ``end`` is the position of the first unprocessed byte """ if type_ is bytes: return (rlp[start:start + length], start + length) elif type_ is list: items = [] next_item_start = start end = next_item_start + length while next_item_start < end: # item, next_item_start = consume_item(rlp, next_item_start) t, l, s = consume_length_prefix(rlp, next_item_start) item, next_item_start = consume_payload(rlp, s, t, l) items.append(item) if next_item_start > end: raise DecodingError('List length prefix announced a too small ' 'length', rlp) return (items, next_item_start) else: raise TypeError('Type must be either list or bytes')
def consume_payload(rlp, prefix, start, type_, length): """Read the payload of an item from an RLP string. :param rlp: the rlp string to read from :param type_: the type of the payload (``bytes`` or ``list``) :param start: the position at which to start reading :param length: the length of the payload in bytes :returns: a tuple ``(item, per_item_rlp, end)``, where ``item`` is the read item, per_item_rlp is a list containing the RLP encoding of each item and ``end`` is the position of the first unprocessed byte """ if type_ is bytes: item = rlp[start:start + length] return (item, [prefix + item], start + length) elif type_ is list: items = [] per_item_rlp = [] list_rlp = prefix next_item_start = start end = next_item_start + length while next_item_start < end: p, t, l, s = consume_length_prefix(rlp, next_item_start) item, item_rlp, next_item_start = consume_payload(rlp, p, s, t, l) per_item_rlp.append(item_rlp) # When the item returned above is a single element, item_rlp will also contain a # single element, but when it's a list, the first element will be the RLP of the # whole List, which is what we want here. list_rlp += item_rlp[0] items.append(item) per_item_rlp.insert(0, list_rlp) if next_item_start > end: raise DecodingError( 'List length prefix announced a too small ' 'length', rlp) return (items, per_item_rlp, next_item_start) else: raise TypeError('Type must be either list or bytes')
def decode_raw(item, strict, preserve_per_item_rlp): try: return rusty_rlp.decode_raw(item, strict, preserve_per_item_rlp) except (TypeError, rusty_rlp.DecodingError) as e: raise DecodingError(e, item)