from dataclasses import dataclass import binascii from typing import * __all__ = [ "Fixed", "Unknown", "Bytes", "Ascii", "Int", ] T = TypeVar("T") Extractor = Callable[[bytes], T] Formatter = Callable[[T], str] default_bytes_formatter: Formatter[bytes] = lambda data: binascii.hexlify(data).decode() def assert_extractor(pattern: bytes) -> Extractor[bytes]: def inner(data: bytes) -> bytes: assert data == pattern return pattern return inner Backref = str @dataclass class FieldType(Generic[T]): """ Dataclass to store all the information necessary to parse a packet field. Fields: ------- name : Optional[str] Field Name. length : Union[int, Backref] How many bytes the field has. Can be either an integer, or the name of a previous field containing one. extractor : Extractor[T] Function to parse the bytes of the field. May throw Exceptions to signal parsing errors. formatter : Formatter[T] Function to get a string representation of the field. """ name: Optional[str] length: Union[int, Backref] extractor: Extractor[T] formatter: Formatter[T] = str def Fixed(pattern: bytes, **kwargs) -> FieldType[bytes]: return FieldType(None, len(pattern), assert_extractor(pattern), **kwargs) def Unknown( length: Union[int, Backref], name: Optional[str] = None, formatter: Formatter[bytes] = default_bytes_formatter, ) -> FieldType[bytes]: return FieldType(name, length, lambda data: data, formatter=formatter) def Bytes( name: str, length: Union[int, Backref], extractor: Extractor[bytes] = lambda data: data, formatter: Formatter[bytes] = default_bytes_formatter, ) -> FieldType[bytes]: return FieldType(name, length, extractor, formatter=formatter) def Ascii( name: str, length: Union[int, Backref], extractor: Extractor[str] = bytes.decode, **kwargs ) -> FieldType[str]: return FieldType(name, length, extractor, **kwargs) def Int( name: str, length: Union[int, Backref], extractor: Extractor[int] = lambda data: int.from_bytes(data, "big"), **kwargs ) -> FieldType[int]: return FieldType(name, length, extractor, **kwargs)