from abc import ABCMeta, abstractmethod
from struct import pack, unpack_from, error
from typing import (
Any,
Callable,
Generic,
Iterator,
Optional,
Union,
Tuple,
Type,
TypeVar,
cast,
)
from .bits import rol
__all__ = [
"QWORD",
"DWORD",
"WORD",
"BYTE",
"CHAR",
"UInt64",
"UInt32",
"UInt16",
"UInt8",
"Int64",
"Int32",
"Int16",
"Int8",
]
T = TypeVar("T", bound="IntType")
[docs]class IntTypeBase(object):
"""
Base class representing all IntType instances
"""
pass
[docs]class MultipliedIntTypeBase(IntTypeBase, Generic[T], metaclass=ABCMeta):
"""
Base class representing all MultipliedIntType instances
"""
@staticmethod
@abstractmethod
def unpack(other: bytes, offset: int = 0) -> Optional[Tuple[T, ...]]:
raise NotImplementedError()
[docs]class IntType(int, IntTypeBase, metaclass=MetaIntType):
"""
Fixed-size variant of int type with C-style operators and casting
Supports ctypes-like multiplication for unpacking tuple of values
* Unsigned types:
:class:`UInt64` (:class:`QWORD`), :class:`UInt32` (:class:`DWORD`),
:class:`UInt16` (:class:`WORD`), :class:`UInt8` (:class:`BYTE` or :class:`CHAR`)
* Signed types:
:class:`Int64`, :class:`Int32`, :class:`Int16`, :class:`Int8`
IntTypes are derived from :class:`int` type, so they are fully compatible with other numeric types
.. code-block:: python
res = u32(0x8080FFFF) << 16 | 0xFFFF
> 0xFFFFFFFF
res = Int32(res)
> -1
Using IntTypes you don't need to mask everything with 0xFFFFFFFF, only if you remember about appropriate casting.
.. code-block:: python
from malduck import DWORD
def rol7_hash(name: bytes):
hh = 0
for c in name:
hh = DWORD(x).rol(7) ^ c
return x
def sdbm_hash(name: bytes):
hh = 0
for c in name:
hh = DWORD(c) + (hh << 6) + (hh << 16) - hh
return hh
Type coercion between native and fixed integers depends on LHS type:
.. code-block:: python
UInt32 = UInt32 + int
int = int + UInt32
IntTypes can be multiplied like ctypes classes for unpacking tuple of values:
.. code-block:: python
values = (BYTE * 3).unpack('\\x01\\x02\\x03')
values -> (1, 2, 3)
"""
bits = 64
signed = False
fmt = "Q"
def __new__(cls: MetaIntType, value: Any) -> "IntType":
value = int(value) & cls.mask
if cls.signed:
value |= -(value & cls.invert_mask)
construct = cast(Callable[[MetaIntType, Any], IntType], int.__new__)
return construct(cls, value)
def __add__(self, other: Any) -> "IntType":
res = super().__add__(other)
return self.__class__(res)
def __sub__(self, other: Any) -> "IntType":
res = super().__sub__(other)
return self.__class__(res)
def __mul__(self, other: Any) -> "IntType":
res = super().__mul__(other)
return self.__class__(res)
def __truediv__(self, other: Any) -> "IntType":
res = super().__truediv__(other)
return self.__class__(res)
def __floordiv__(self, other: Any) -> "IntType":
res = super().__floordiv__(other)
return self.__class__(res)
def __and__(self, other: Any) -> "IntType":
res = super().__and__(other)
return self.__class__(res)
def __xor__(self, other: Any) -> "IntType":
res = super().__xor__(other)
return self.__class__(res)
def __or__(self, other: Any) -> "IntType":
res = super().__or__(other)
return self.__class__(res)
def __lshift__(self, other: Any) -> "IntType":
res = super().__lshift__(other)
return self.__class__(res)
def __pos__(self) -> "IntType":
res = super().__pos__()
return self.__class__(res)
def __abs__(self) -> "IntType":
res = super().__abs__()
return self.__class__(res)
def __rshift__(self, other: Any) -> "IntType":
res = int.__rshift__(int(self) & self.__class__.mask, other)
return self.__class__(res)
def __neg__(self) -> "IntType":
res = (int(self) ^ self.__class__.mask) + 1
return self.__class__(res)
def __invert__(self) -> "IntType":
res = int(self) ^ self.__class__.mask
return self.__class__(res)
[docs] def rol(self, other) -> "IntType":
"""Bitwise rotate left"""
return self.__class__(rol(int(self), other, bits=self.bits))
[docs] def ror(self, other) -> "IntType":
"""Bitwise rotate right"""
return self.rol(self.bits - other)
[docs] def pack(self) -> bytes:
"""Pack value into bytes with little-endian order"""
return pack("<" + self.fmt, int(self))
[docs] def pack_be(self) -> bytes:
"""Pack value into bytes with big-endian order"""
return pack(">" + self.fmt, int(self))
[docs] @classmethod
def unpack(
cls, other: bytes, offset: int = 0, fixed: bool = True
) -> Union["IntType", int, None]:
"""
Unpacks single value from provided buffer with little-endian order
:param other: Buffer object containing value to unpack
:type other: bytes
:param offset: Buffer offset
:type offset: int
:param fixed: Convert to fixed-size integer (IntType instance)
:type fixed: bool (default: True)
:rtype: IntType instance or None if there are not enough data to unpack
.. warning::
Fixed-size integer operations are 4-5 times slower than equivalent on built-in integer types
"""
try:
ret = unpack_from("<" + cls.fmt, other, offset=offset)
except error:
return None
return cls(ret[0]) if fixed else ret[0]
[docs] @classmethod
def unpack_be(
cls, other: bytes, offset: int = 0, fixed: bool = True
) -> Union["IntType", int, None]:
"""
Unpacks single value from provided buffer with big-endian order
:param other: Buffer object containing value to unpack
:type other: bytes
:param offset: Buffer offset
:type offset: int
:param fixed: Convert to fixed-size integer (IntType instance)
:type fixed: bool (default: True)
:rtype: IntType instance or None if there are not enough data to unpack
.. warning::
Fixed-size integer operations are 4-5 times slower than equivalent on built-in integer types
"""
try:
ret = unpack_from(">" + cls.fmt, other, offset=offset)
except error:
return None
return cls(ret[0]) if fixed else ret[0]
[docs]class UInt64(IntType):
bits = 64
signed = False
fmt = "Q"
[docs]class UInt32(IntType):
bits = 32
signed = False
fmt = "I"
[docs]class UInt16(IntType):
bits = 16
signed = False
fmt = "H"
[docs]class UInt8(IntType):
bits = 8
signed = False
fmt = "B"
[docs]class Int64(IntType):
bits = 64
signed = True
fmt = "q"
[docs]class Int32(IntType):
bits = 32
signed = True
fmt = "i"
[docs]class Int16(IntType):
bits = 16
signed = True
fmt = "h"
[docs]class Int8(IntType):
bits = 8
signed = True
fmt = "b"
QWORD = UInt64
DWORD = UInt32
WORD = UInt16
CHAR = BYTE = UInt8