-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from Bhargavasomu/uint_and_hashN
Add serialization/deserialization functionality for Integers
- Loading branch information
Showing
8 changed files
with
505 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from ssz.exceptions import ( | ||
DeserializationError, | ||
SerializationError, | ||
) | ||
|
||
|
||
class Hash: | ||
""" | ||
A sedes for hashes (hash<N>). | ||
""" | ||
num_bytes = 0 | ||
|
||
def __init__(self, num_bytes): | ||
if num_bytes <= 0: | ||
raise ValueError( | ||
"Number of bytes should be non-negavtive" | ||
) | ||
|
||
self.num_bytes = num_bytes | ||
|
||
def serialize(self, val): | ||
if len(val) != self.num_bytes: | ||
raise SerializationError( | ||
"Can only serialize values of {} bytes".format(self.num_bytes), | ||
val | ||
) | ||
|
||
return val | ||
|
||
def deserialize_segment(self, data, start_index): | ||
""" | ||
Deserialize the data from the given start_index | ||
""" | ||
# Make sure we have sufficient data for deserializing | ||
if len(data) < self.num_bytes + start_index: | ||
raise DeserializationError( | ||
'Insufficient data for deserializing', | ||
data | ||
) | ||
end_index = start_index + self.num_bytes | ||
return data[start_index:end_index], end_index | ||
|
||
def deserialize(self, data): | ||
deserialized_data, end_index = self.deserialize_segment(data, 0) | ||
if end_index != len(data): | ||
raise DeserializationError( | ||
'Data to be deserialized is too long', | ||
data | ||
) | ||
|
||
return deserialized_data | ||
|
||
|
||
hash32 = Hash(32) | ||
address = Hash(20) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from ssz.exceptions import ( | ||
DeserializationError, | ||
SerializationError, | ||
) | ||
|
||
|
||
class UnsignedInteger: | ||
""" | ||
A sedes for integers (uint<N>). | ||
""" | ||
num_bytes = 0 | ||
|
||
def __init__(self, num_bits): | ||
# Make sure the number of bits are multiple of 8 | ||
if num_bits % 8 != 0: | ||
raise ValueError( | ||
"Number of bits should be multiple of 8" | ||
) | ||
if num_bits <= 0: | ||
raise ValueError( | ||
"Number of bits should be greater than 0" | ||
) | ||
self.num_bytes = num_bits // 8 | ||
|
||
def serialize(self, val): | ||
if isinstance(val, bool) or not isinstance(val, int): | ||
raise SerializationError( | ||
'As per specified sedes object, can only serialize non-negative integer values', | ||
val | ||
) | ||
if val < 0: | ||
raise SerializationError( | ||
'As per specified sedes object, can only serialize non-negative integer values', | ||
val | ||
) | ||
|
||
try: | ||
serialized_obj = val.to_bytes(self.num_bytes, 'big') | ||
except OverflowError as err: | ||
raise SerializationError('As per specified sedes object, %s' % err, val) | ||
|
||
return serialized_obj | ||
|
||
def deserialize_segment(self, data, start_index): | ||
""" | ||
Deserialize the data from the given start_index | ||
""" | ||
# Make sure we have sufficient data for deserializing | ||
if len(data) + start_index < self.num_bytes: | ||
raise DeserializationError( | ||
'Insufficient data for deserializing', | ||
data | ||
) | ||
end_index = start_index + self.num_bytes | ||
return int.from_bytes(data[start_index:end_index], 'big'), end_index | ||
|
||
def deserialize(self, data): | ||
deserialized_data, end_index = self.deserialize_segment(data, 0) | ||
if end_index != len(data): | ||
raise DeserializationError( | ||
'Data to be deserialized is too long', | ||
data | ||
) | ||
|
||
return deserialized_data | ||
|
||
|
||
uint8 = UnsignedInteger(8) | ||
uint16 = UnsignedInteger(16) | ||
uint24 = UnsignedInteger(24) | ||
uint32 = UnsignedInteger(32) | ||
uint40 = UnsignedInteger(40) | ||
uint48 = UnsignedInteger(48) | ||
uint56 = UnsignedInteger(56) | ||
uint64 = UnsignedInteger(64) | ||
uint128 = UnsignedInteger(128) | ||
uint256 = UnsignedInteger(256) | ||
uint384 = UnsignedInteger(384) | ||
uint512 = UnsignedInteger(512) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import pytest | ||
|
||
from ssz import ( | ||
DeserializationError, | ||
SerializationError, | ||
decode, | ||
encode, | ||
) | ||
from ssz.sedes import ( | ||
Hash, | ||
address, | ||
hash32, | ||
) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'num_bytes', | ||
( | ||
0, | ||
-10, | ||
-100, | ||
), | ||
) | ||
def test_reject_hash_object_negative_bytes(num_bytes): | ||
with pytest.raises(ValueError): | ||
Hash(num_bytes) | ||
|
||
|
||
def test_hash_serialize_values(): | ||
for num_bytes in range(1, 33): | ||
value = b'\x01' * num_bytes | ||
assert Hash(num_bytes).serialize(value) == value | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'value,sedes', | ||
( | ||
(b'\x01' * 32, Hash(16)), | ||
(b'\x01' * 32, Hash(20)), | ||
(b'\x01' * 16, Hash(20)), | ||
(b'\x01' * 16, hash32), | ||
(b'\x01' * 32, Hash(20)), | ||
), | ||
) | ||
def test_hash_serialize_bad_values(value, sedes): | ||
with pytest.raises(SerializationError): | ||
sedes.serialize(value) | ||
|
||
|
||
def test_hash_deserialize_values(): | ||
for num_bytes in range(1, 33): | ||
value = b'\x01' * num_bytes | ||
assert Hash(num_bytes).deserialize(value) == value | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'value,sedes', | ||
( | ||
# Values too short | ||
(b'\x01' * 15, Hash(16)), | ||
(b'\x01' * 16, Hash(20)), | ||
(b'\x01' * 10, Hash(20)), | ||
(b'\x01' * 5, Hash(20)), | ||
(b'\x01' * 16, Hash(32)), | ||
# Values too long | ||
(b'\x01' * 20, Hash(16)), | ||
(b'\x01' * 25, Hash(20)), | ||
(b'\x01' * 40, Hash(32)), | ||
), | ||
) | ||
def test_hash_deserialize_bad_values(value, sedes): | ||
with pytest.raises(DeserializationError): | ||
sedes.deserialize(value) | ||
|
||
|
||
def test_hash_round_trip(): | ||
for num_bytes in range(1, 33): | ||
value = b'\x01' * num_bytes | ||
sedes_obj = Hash(num_bytes) | ||
assert sedes_obj.deserialize(sedes_obj.serialize(value)) == value | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'value', | ||
( | ||
b'\x00' * 20, | ||
b'\x01' * 20, | ||
), | ||
) | ||
def test_address_round_trip(value): | ||
assert address.deserialize(address.serialize(value)) == value | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'value,sedes', | ||
( | ||
(b'\x01' * 32, 'hash32'), | ||
(b'\x01' * 32, hash32), | ||
(b'\x01' * 32, Hash(32)), | ||
(b'\x01' * 64, Hash(64)), | ||
), | ||
) | ||
def test_hash_round_trip_codec(value, sedes): | ||
if isinstance(sedes, str): | ||
sedes_obj = eval(sedes) | ||
else: | ||
sedes_obj = sedes | ||
assert decode(encode(value, sedes), sedes_obj) == value | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'value', | ||
( | ||
b'\x00' * 20, | ||
b'\x01' * 20, | ||
), | ||
) | ||
def test_address_round_trip_codec(value): | ||
assert decode(encode(value, address), address) == value |
Oops, something went wrong.