Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
jnnk committed Mar 23, 2015
2 parents 706a4fa + f1856f6 commit db69748
Show file tree
Hide file tree
Showing 21 changed files with 289 additions and 128 deletions.
6 changes: 6 additions & 0 deletions .travis.yml
@@ -0,0 +1,6 @@
language: python
python:
- "2.7"
- "3.4"
install: python setup.py install
script: py.test
45 changes: 27 additions & 18 deletions rlp/codec.py
@@ -1,18 +1,22 @@
import abc
import collections
from functools import partial
from itertools import izip, imap
import sys
from .exceptions import EncodingError, DecodingError
from .utils import Atomic
from .utils import (Atomic, str_to_bytes, is_integer, bytes_to_int_array,
ascii_chr)
from .sedes.binary import Binary as BinaryClass
from .sedes import big_endian_int, binary
from .sedes.lists import List, is_sedes


if sys.version_info.major == 2:
from itertools import imap as map


def encode(obj, sedes=None, infer_serializer=True):
"""Encode a Python object in RLP format.
By default, the object is serialized in a suitable way first (using
:func:`rlp.infer_sedes`) and then encoded. Serialization can be
:func:`rlp.infer_sedes`) and then encoded. Serialization can be
explicitly suppressed by setting `infer_serializer` to ``False`` and not
passing an alternative as `sedes`.
Expand All @@ -39,12 +43,12 @@ def encode(obj, sedes=None, infer_serializer=True):
def encode_raw(item):
"""RLP encode (a nested sequence of) :class:`Atomic`s."""
if isinstance(item, Atomic):
if len(item) == 1 and ord(item[0]) < 128:
return str(item)
payload = str(item)
if len(item) == 1 and bytes_to_int_array(item)[0] < 128:
return str_to_bytes(item)
payload = str_to_bytes(item)
prefix_offset = 128 # string
elif isinstance(item, collections.Sequence):
payload = ''.join(imap(encode_raw, item))
payload = b''.join(map(encode_raw, item))
prefix_offset = 192 # list
else:
msg = 'Cannot encode object of type {0}'.format(type(item).__name__)
Expand All @@ -54,6 +58,7 @@ def encode_raw(item):
prefix = length_prefix(len(payload), prefix_offset)
except ValueError:
raise EncodingError('Item too big to encode', item)

return prefix + payload


Expand All @@ -65,25 +70,28 @@ def length_prefix(length, offset):
list
"""
if length < 56:
return chr(offset + length)
return ascii_chr(offset + length)
elif length < 256**8:
length_string = big_endian_int.serialize(length)
return chr(offset + 56 - 1 + len(length_string)) + length_string
return ascii_chr(offset + 56 - 1 + len(length_string)) + length_string
else:
raise ValueError('Length greater than 256**8')


def consume_length_prefix(rlp, start):
"""Read a length prefix from an RLP string.
:param rlp: the rlp 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 = ord(rlp[start])
if isinstance(rlp, str):
rlp = str_to_bytes(rlp)

b0 = bytes_to_int_array(rlp)[start]
if b0 < 128: # single byte
return (str, 1, start)
elif b0 < 128 + 56: # short string
Expand All @@ -94,7 +102,7 @@ def consume_length_prefix(rlp, start):
return (str, l, start + 1 + ll)
elif b0 < 192 + 56: # short list
return (list, b0 - 192, start + 1)
else: # long list
else: # long list
ll = b0 - 192 - 56 + 1
l = big_endian_int.deserialize(rlp[start + 1:start + 1 + ll])
if l < 56:
Expand Down Expand Up @@ -128,9 +136,10 @@ def consume_payload(rlp, start, type_, length):
else:
raise TypeError('Type must be either list or str')


def consume_item(rlp, start):
"""Read an item from an RLP string.
:param rlp: the rlp string to read from
:param start: the position at which to start reading
:returns: a tuple ``(item, end)`` where ``item`` is the read item and
Expand Down Expand Up @@ -178,11 +187,11 @@ def infer_sedes(obj):
"""
if is_sedes(obj.__class__):
return obj.__class__
if isinstance(obj, (int, long)) and obj >= 0:
if is_integer(obj) and obj >= 0:
return big_endian_int
if isinstance(obj, (str, unicode, bytearray)):
if BinaryClass.is_valid_type(obj):
return binary
if isinstance(obj, collections.Sequence):
return List(imap(infer_sedes, obj))
return List(map(infer_sedes, obj))
msg = 'Did not find sedes handling type {}'.format(type(obj).__name__)
raise TypeError(msg)
9 changes: 5 additions & 4 deletions rlp/exceptions.py
Expand Up @@ -5,7 +5,7 @@ class RLPException(Exception):

class EncodingError(RLPException):
"""Exception raised if encoding fails.
:ivar obj: the object that could not be encoded
"""

Expand All @@ -16,7 +16,7 @@ def __init__(self, message, obj):

class DecodingError(RLPException):
"""Exception raised if decoding fails.
:ivar rlp: the RLP string that could not be decoded
"""

Expand All @@ -27,17 +27,18 @@ def __init__(self, message, rlp):

class SerializationError(RLPException):
"""Exception raised if serialization fails.
:ivar obj: the object that could not be serialized
"""

def __init__(self, message, obj):
super(SerializationError, self).__init__(message)
self.obj = obj


class DeserializationError(RLPException):
"""Exception raised if deserialization fails.
:ivar serial: the decoded RLP string that could not be deserialized
"""

Expand Down
11 changes: 6 additions & 5 deletions rlp/lazy.py
@@ -1,5 +1,5 @@
from collections import Sequence
from .exceptions import DecodingError, DeserializationError
from .exceptions import DecodingError
from .codec import consume_length_prefix, consume_payload


Expand All @@ -16,7 +16,7 @@ def decode_lazy(rlp, sedes=None, **sedes_kwargs):
deserialized individually. In both cases, `sedes_kwargs` are passed on.
Note that, if a deserializer is used, only "horizontal" but not
"vertical lazyness" can be preserved.
:param rlp: the RLP string to decode
:param sedes: an object implementing a method ``deserialize(code)``
which is used as described above, or ``None`` if no
Expand All @@ -38,12 +38,13 @@ def decode_lazy(rlp, sedes=None, **sedes_kwargs):
else:
return item


def consume_item_lazy(rlp, start):
"""Read an item from an RLP string lazily.
If the length prefix announces a string, the string is read; if it
announces a list, a :class:`LazyList` is created.
:param rlp: the rlp string to read from
:param start: the position at which to start reading
:returns: a tuple ``(item, end)`` where ``item`` is the read string or a
Expand Down Expand Up @@ -100,7 +101,7 @@ def next(self):
def __getitem__(self, i):
try:
while len(self.elements_) <= i:
next(self)
self.next()
except StopIteration:
assert self.index == self.end
raise IndexError('Index %d out of range' % i)
Expand All @@ -110,7 +111,7 @@ def __len__(self):
if not self.len_:
try:
while True:
next(self)
self.next()
except StopIteration:
self.len_ = len(self.elements_)
return self.len_
19 changes: 10 additions & 9 deletions rlp/sedes/big_endian_int.py
@@ -1,4 +1,5 @@
from ..exceptions import DeserializationError, SerializationError
from ..utils import int_to_big_endian, is_integer, encode_hex


class BigEndianInt(object):
Expand All @@ -12,33 +13,33 @@ def __init__(self, l=None):
self.l = l

def serialize(self, obj):
if not isinstance(obj, (int, long)):
if not is_integer(obj):
raise SerializationError('Can only serialize integers', obj)
if self.l is not None and obj >= 256**self.l:
raise SerializationError('Integer too large (does not fit in {} '
'bytes)'.format(self.l), obj)
if obj < 0:
raise SerializationError('Cannot serialize negative integers', obj)

cs = []
while obj > 0:
cs.append(chr(obj % 256))
obj /= 256
s = ''.join(reversed(cs))
if obj == 0:
s = b''
else:
s = int_to_big_endian(obj)

if self.l is not None:
return '\x00' * max(0, self.l - len(s)) + s
return b'\x00' * max(0, self.l - len(s)) + s
else:
return s

def deserialize(self, serial):
if self.l is not None and len(serial) != self.l:
raise DeserializationError('Invalid serialization (wrong size)',
serial)
if self.l is None and len(serial) > 1 and serial[0] == '\x00':
if self.l is None and len(serial) > 1 and serial[0] == 0:
raise DeserializationError('Invalid serialization (not minimal '
'length)', serial)
return int(serial.encode('hex') or '0', 16)

serial = serial or b'\x00'
return int(encode_hex(serial), 16)

big_endian_int = BigEndianInt()
39 changes: 29 additions & 10 deletions rlp/sedes/binary.py
@@ -1,9 +1,11 @@
import sys
from ..exceptions import SerializationError, DeserializationError
from ..utils import Atomic, str_to_bytes, bytes_to_str


class Binary(object):
"""A sedes object for binary data of certain length.
:param min_length: the minimal length in bytes or `None` for no lower limit
:param max_length: the maximal length in bytes or `None` for no upper limit
:param allow_empty: if true, empty strings are considered valid even if
Expand All @@ -20,23 +22,40 @@ def fixed_length(cls, l, allow_empty=False):
"""Create a sedes for binary data with exactly `l` bytes."""
return cls(l, l, allow_empty=allow_empty)

@classmethod
def is_valid_type(cls, obj):
if sys.version_info.major == 2:
return isinstance(obj, (str, unicode, bytearray))
else:
return isinstance(obj, (str, bytes))

def is_valid_length(self, l):
return any((self.min_length <= l <= self.max_length,
self.allow_empty and l == 0))

def serialize(self, obj):
if not isinstance(obj, (str, unicode, bytearray)):
raise SerializationError('Object is not a string', obj)
if not self.is_valid_length(len(str(obj))):
raise SerializationError('String has invalid length', obj)
return str(obj)
if not Binary.is_valid_type(obj):
raise SerializationError('Object is not a serializable ({})'.format(type(obj)), obj)

if isinstance(obj, str):
serial = str_to_bytes(obj)
else:
serial = obj

if not self.is_valid_length(len(serial)):
raise SerializationError('Object has invalid length', serial)

return serial

def deserialize(self, serial):
b = str(serial)
if self.is_valid_length(len(b)):
return b
if not isinstance(serial, Atomic):
m = 'Objects of type {} cannot be deserialized'
raise DeserializationError(m.format(type(serial).__name__), serial)

if self.is_valid_length(len(serial)):
return bytes_to_str(serial)
else:
raise DeserializationError('String has invalid length', serial)
raise DeserializationError('{} has invalid length'.format(type(serial)), serial)


binary = Binary()

0 comments on commit db69748

Please sign in to comment.