95 lines
2.3 KiB
Python
95 lines
2.3 KiB
Python
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)
|