Skip to content
This repository was archived by the owner on Sep 26, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions common/net/bigsize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
def encode(value):
"""
Encodes a value to BigSize.

Args:
value (:obj:`int`): the integer value to be encoded.

Returns:
:obj:`bytes`: the BigSize encoding of the given value.

Raises:
:obj:`TypeError`: If the provided value is not an integer.
:obj:`ValueError`: If the provided value is negative or bigger than ``pow(2, 64)``.
"""

if not isinstance(value, int):
raise TypeError(f"value must be integer, {type(value)} received")

if value < 0:
raise ValueError(f"value must be a positive integer, {value} received")

if value < pow(2, 8) - 3:
return value.to_bytes(1, "big")
elif value < pow(2, 16):
return b"\xfd" + value.to_bytes(2, "big")
elif value < pow(2, 32):
return b"\xfe" + value.to_bytes(4, "big")
elif value <= pow(2, 64):
return b"\xff" + value.to_bytes(8, "big")
Comment thread
bigspider marked this conversation as resolved.
else:
raise ValueError("BigSize can only encode up to 8-byte values")


def decode(value):
"""
Decodes a value fro BigSize.
Comment thread
sr-gi marked this conversation as resolved.
Outdated

Args:
value (:obj:`bytes`): the value to be decoded.

Returns:
:obj:`int`: the integer decoding of the provided value.

Raises:
:obj:`TypeError`: If the provided value is not in bytes.
:obj:`ValueError`: If the provided value is bigger than 9-bytes or the value is not properly encoded.
"""

if not isinstance(value, bytes):
raise TypeError(f"value must be bytes, {type(value)} received")

if len(value) > 9:
raise ValueError(f"value must be, at most, 9-bytes long, {len(value)} received")
Comment thread
bigspider marked this conversation as resolved.

if len(value) > 1:
prefix = value[0]
decoded_value = int.from_bytes(value[1:], "big")
else:
prefix = None
decoded_value = int.from_bytes(value, "big")

if not prefix and len(value) == 1 and decoded_value < pow(2, 8) - 3:
return decoded_value
elif prefix == 253 and len(value) == 3 and pow(2, 8) - 3 <= decoded_value < pow(2, 16):
Comment thread
bigspider marked this conversation as resolved.
Outdated
return decoded_value
elif prefix == 254 and len(value) == 5 and pow(2, 16) <= decoded_value < pow(2, 32):
return decoded_value
elif prefix == 255 and len(value) == 9 and pow(2, 32) <= decoded_value:
return decoded_value
else:
raise ValueError("value is not properly encoded")
Comment thread
bigspider marked this conversation as resolved.
Outdated


def parse(value):
"""
Parses a BigSize from a bytearray.

Args:
value (:obj:`bytes`): the bytearray from where the BigSize value will be parsed.

Returns:
:obj:`tuple`: A 2 items tuple containing the parsed BigSize and its encoded length (offset of the bytearray).
Comment thread
sr-gi marked this conversation as resolved.
Outdated

Raises:
:obj:`TypeError`: If the provided value is not in bytes.
:obj:`ValueError`: If the provided value is not, at least, 1-byte long or if the value cannot be parsed.
"""

if not isinstance(value, bytes):
raise TypeError("value must be bytes")
if len(value) < 1:
raise ValueError("value must be at least 1-byte long")

prefix = value[0]

if prefix < 253:
# prefix is actually the value to be parsed
return decode(value[0:1]), 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One problem with this take-what-you-need-and-return-its-length is that it is not well-suited for unbuffered streams, since we can't take 9 bytes, parse them, and then reset the stream back by the remainder of the bytes. I usually use io.RawBaseIO as argument, wrapping str and bytes if necessary, that removes the need for this type of bookkeeping.

else:
if prefix == 253:
return decode(value[0:3]), 3
elif prefix == 254:
return decode(value[0:5]), 5
else:
return decode(value[0:9]), 9
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the cases with a prefix, these calls will pass to decode an array that is too short if the value array is too short, for example value[0:3] might have less than 3 bytes.
While decode correctly raises in that case, perhaps checking for correct length here might be more explicit.
If I not, at least a comment making this fact explicit would be nice.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a comment. This was intentional to avoid double checking the same thing.

41 changes: 41 additions & 0 deletions common/net/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import common.net.bigsize as bigsize


def message_sanity_checks(message, expected_type, min_len, tlv=False):
"""
Runs sanity checks to a received byte-encoded message, such as checking it's minimum length or message type.
Comment thread
sr-gi marked this conversation as resolved.
Outdated

Args:
message (:obj:`bytes`): the bytes-encoded message.
expected_type (:obj:`str`): the expected type of the message.
min_len (:obj:`int`): the minimum expected length of the message.
tlv (:obj:`bool`): whether the message is a tlv record or not.

Raises:
:obj:`TypeError`: If the provided message is not in bytes.
:obj:`ValueError`: If the provided message is not long enough or not of the expected type.
"""

if not isinstance(message, bytes):
raise TypeError("message be must a bytearray")
if not isinstance(expected_type, bytes):
raise TypeError("expected_type be must bytes")
if not isinstance(min_len, int):
raise TypeError("min_len be must int")
if not isinstance(tlv, bool):
raise TypeError("tlv be must bool if set")
if len(message) < min_len:
raise ValueError(f"message be must at least {min_len}-byte long")

if tlv:
tlv_type, type_offset = bigsize.parse(message)
tlv_type_byte = tlv_type.to_bytes(type_offset, "big")
if tlv_type_byte != expected_type:
raise ValueError(
f"Wrong message format. types do not match (expected: {expected_type}, received: {tlv_type_byte}"
)
else:
if message[:2] != expected_type:
raise ValueError(
f"Wrong message format. types do not match (expected: {expected_type}, received: {message[:2]}"
)