# basic_types.py
# Contains the basic types used project-wides
#
# - License : MIT - See LICENSE file
# - Project : Scrutiny Debugger (github.com/scrutinydebugger/scrutiny-main)
#
# Copyright (c) 2022 Scrutiny Debugger
__all__ = [
'Endianness',
'DataTypeType',
'DataTypeSize',
'EmbeddedDataType',
'RuntimePublishedValue',
'MemoryRegion',
'WatchableType',
'ServerDatastoreContentType',
]
from enum import Enum
from dataclasses import dataclass
from scrutiny.tools import validation
from scrutiny.tools.typing import *
[docs]
@dataclass(frozen=True, slots=True)
class MemoryRegion:
"""(Immutable struct)
Represent a memory region spanning from ``start`` to ``start+size-1`` inclusively"""
start: int
"""Start address of the region"""
size: int
"""Size in bytes of the region"""
def __post_init__(self) -> None:
validation.assert_int_range(self.start, 'start', minval=0)
validation.assert_int_range(self.size, 'size', minval=0)
def touches(self, other: "MemoryRegion") -> bool:
if self.size <= 0 or other.size <= 0:
return False
if self.start >= other.start + other.size:
return False
if other.start >= self.start + self.size:
return False
return True
@property
def end(self) -> int:
return max(self.start, self.start + self.size - 1)
[docs]
class Endianness(Enum):
"""(Enum) Represent an data storage endianness"""
Little = 0
"""Little endian. 0x12345678 is stored as 78 56 34 12 """
Big = 1
"""Big endian. 0x12345678 is stored as 12 34 56 78 """
def to_str(self) -> str:
if self == Endianness.Little:
return 'little'
elif self == Endianness.Big:
return 'big'
else:
raise ValueError(f"Unsupported endianness {self}")
@classmethod
def from_str(cls, s: str) -> "Endianness":
s = s.lower().strip()
if s == 'little':
return Endianness.Little
if s == 'big':
return Endianness.Big
raise ValueError(f"Invalid endianness {s}")
class DataTypeType(Enum):
_sint = (0 << 4)
_uint = (1 << 4)
_float = (2 << 4)
_bool = (3 << 4)
_cfloat = (4 << 4)
_struct = (5 << 4)
_pointer = (6 << 4)
_NA = (0xF << 4)
class DataTypeSize(Enum):
_8 = 0
_16 = 1
_32 = 2
_64 = 3
_128 = 4
_256 = 5
_NA = 0xF
[docs]
class EmbeddedDataType(Enum):
"""
(Enum)
Represent a datatype that can be read from a device.
The embedded library has the same definition of datatypes as this one. They needs to match.
Not all datatype are supported. (cfloat or >64 bits)
"""
sint8 = DataTypeType._sint.value | DataTypeSize._8.value
sint16 = DataTypeType._sint.value | DataTypeSize._16.value
sint32 = DataTypeType._sint.value | DataTypeSize._32.value
sint64 = DataTypeType._sint.value | DataTypeSize._64.value
sint128 = DataTypeType._sint.value | DataTypeSize._128.value
sint256 = DataTypeType._sint.value | DataTypeSize._256.value
uint8 = DataTypeType._uint.value | DataTypeSize._8.value
uint16 = DataTypeType._uint.value | DataTypeSize._16.value
uint32 = DataTypeType._uint.value | DataTypeSize._32.value
uint64 = DataTypeType._uint.value | DataTypeSize._64.value
uint128 = DataTypeType._uint.value | DataTypeSize._128.value
uint256 = DataTypeType._uint.value | DataTypeSize._256.value
float8 = DataTypeType._float.value | DataTypeSize._8.value
float16 = DataTypeType._float.value | DataTypeSize._16.value
float32 = DataTypeType._float.value | DataTypeSize._32.value
float64 = DataTypeType._float.value | DataTypeSize._64.value
float128 = DataTypeType._float.value | DataTypeSize._128.value
float256 = DataTypeType._float.value | DataTypeSize._256.value
cfloat8 = DataTypeType._cfloat.value | DataTypeSize._8.value
cfloat16 = DataTypeType._cfloat.value | DataTypeSize._16.value
cfloat32 = DataTypeType._cfloat.value | DataTypeSize._32.value
cfloat64 = DataTypeType._cfloat.value | DataTypeSize._64.value
cfloat128 = DataTypeType._cfloat.value | DataTypeSize._128.value
cfloat256 = DataTypeType._cfloat.value | DataTypeSize._256.value
ptr8 = DataTypeType._pointer.value | DataTypeSize._8.value
ptr16 = DataTypeType._pointer.value | DataTypeSize._16.value
ptr32 = DataTypeType._pointer.value | DataTypeSize._32.value
ptr64 = DataTypeType._pointer.value | DataTypeSize._64.value
ptr128 = DataTypeType._pointer.value | DataTypeSize._128.value
ptr256 = DataTypeType._pointer.value | DataTypeSize._256.value
bool8 = DataTypeType._bool.value | DataTypeSize._8.value
bool16 = DataTypeType._bool.value | DataTypeSize._16.value
bool32 = DataTypeType._bool.value | DataTypeSize._32.value
bool64 = DataTypeType._bool.value | DataTypeSize._64.value
bool128 = DataTypeType._bool.value | DataTypeSize._128.value
bool256 = DataTypeType._bool.value | DataTypeSize._256.value
struct = DataTypeType._struct.value | DataTypeSize._NA.value
NA = DataTypeType._NA.value | DataTypeSize._NA.value
[docs]
def get_size_bit(self) -> int:
"""Return the size of the datatype in bits. Returns 0 if NA"""
v = self.get_size_8bits()
return v * 8
[docs]
def get_size_8bits(self) -> int:
"""Return the size of the datatype in 8bits bytes. Returns 0 if NA"""
vbytes = (self.value & 0xF)
if DataTypeSize(vbytes) == DataTypeSize._NA:
return 0
return 1 << vbytes
[docs]
def is_integer(self) -> bool:
"""Tells if the datatype is an integer type (sint or uint)"""
type_type = self.value & 0xF0
if type_type in (DataTypeType._sint.value, DataTypeType._uint.value):
return True
return False
[docs]
def is_float(self) -> bool:
"""Tells if a datatype is a floating point value (float)"""
type_type = self.value & 0xF0
# Cfloat???
if type_type in (DataTypeType._float.value, DataTypeType._cfloat.value):
return True
return False
[docs]
def is_signed(self) -> bool:
"""Tells if the datatype support a sign (sint, float, cfloat)"""
type_type = self.value & 0xF0
if type_type in (DataTypeType._sint.value, DataTypeType._float.value, DataTypeType._cfloat.value):
return True
return False
[docs]
def is_pointer(self) -> bool:
"""Tells if the type is a pointer type (independent of the size)"""
type_type = self.value & 0xF0
return type_type == DataTypeType._pointer.value
[docs]
def is_bool(self) -> bool:
type_type = self.value & 0xF0
return type_type == DataTypeType._bool.value
@classmethod
def make(cls, datatype_type: DataTypeType, size: Union[int, DataTypeSize]) -> "EmbeddedDataType":
if isinstance(size, int):
if size == 1:
return cls.make(datatype_type, DataTypeSize._8)
if size == 2:
return cls.make(datatype_type, DataTypeSize._16)
if size == 4:
return cls.make(datatype_type, DataTypeSize._32)
if size == 8:
return cls.make(datatype_type, DataTypeSize._64)
if size == 16:
return cls.make(datatype_type, DataTypeSize._128)
if size == 32:
return cls.make(datatype_type, DataTypeSize._256)
raise ValueError(f"Impossible size given {size}")
else:
return cls(datatype_type.value | size.value)
@dataclass(frozen=True, slots=True)
class RuntimePublishedValue:
"""
(Immutable struct) A Runtime Published Value (RPV) is one of the basic elements that can be read from a target device.
RPVs are defined in the embedded code and known by the server by polling the device.
They don't have a name, they are identified by a 16bits identifier.
The user can add an Alias on a RPV to assign them a name
"""
id: int
"""RPV ID (16bits)"""
datatype: EmbeddedDataType
"""The data type of the value"""
def __post_init__(self) -> None:
validation.assert_int_range(self.id, 'id', 0, 0xFFFF)
validation.assert_type(self.datatype, 'datatype', EmbeddedDataType)
def __repr__(self) -> str:
return "<%s: 0x%x (%s) at 0x%016x>" % (self.__class__.__name__, self.id, self.datatype.name, id(self))
[docs]
class WatchableType(str, Enum):
"""(Enum) Type of watchable available on the server"""
Variable = 'var'
"""A variable found in the device firmware debug symbols"""
RuntimePublishedValue = 'rpv'
"""A readable/writable element identified by a 16bits ID. Explicitly defined in the device firmware source code"""
Alias = 'alias'
"""A symbolic link watchable that can refers to a :attr:`Variable` or a :attr:`RuntimePublishedValue`"""
[docs]
@classmethod
def all(cls) -> List["WatchableType"]:
"""Return the list of valid Watchable types. Mainly for unit testing"""
return [cls.Variable, cls.RuntimePublishedValue, cls.Alias]
def __str__(self) -> str:
return self.value
def to_str(self) -> str:
return str(self)
@classmethod
def from_str(cls, v: str) -> "WatchableType":
return WatchableType(v)
[docs]
class ServerDatastoreContentType(str, Enum):
"""(Enum) Type of items that can be downloaded from the server.
This enum contains all watchable types + some special items.
"""
Variable = 'var'
"""A variable found in the device firmware debug symbols."""
RuntimePublishedValue = 'rpv'
"""A readable/writable element identified by a 16bits ID. Explicitly defined in the device firmware source code"""
Alias = 'alias'
"""A symbolic link watchable that can refers to a :attr:`Variable` or a :attr:`RuntimePublishedValue`"""
VariableFactory = 'var_factory'
"""A data structure containing a pattern that can generate variables. Mainly used for array instantiation"""
[docs]
@classmethod
def all(cls) -> List["ServerDatastoreContentType"]:
"""Return the list of valid items. Mainly for unit testing"""
return [cls.Variable, cls.RuntimePublishedValue, cls.Alias, cls.VariableFactory]
def __str__(self) -> str:
return self.value
def to_str(self) -> str:
return str(self)
@classmethod
def from_str(cls, v: str) -> "ServerDatastoreContentType":
return ServerDatastoreContentType(v)