211 lines
5.3 KiB
Python
211 lines
5.3 KiB
Python
import struct as _struct
|
|
|
|
|
|
class BasicType:
|
|
|
|
def __init__(self,format_character,endianess):
|
|
self.format_character = format_character
|
|
self.endianess = endianess
|
|
self.format_string = endianess + format_character
|
|
self.size = _struct.calcsize(self.format_string)
|
|
|
|
def pack(self,stream,value):
|
|
stream.write(_struct.pack(self.format_string,value))
|
|
|
|
def unpack(self,stream):
|
|
return _struct.unpack(self.format_string,stream.read(self.size))[0]
|
|
|
|
def sizeof(self):
|
|
return self.size
|
|
|
|
|
|
class FixedPointConverter:
|
|
|
|
def __init__(self,integer_type,scale):
|
|
self.integer_type = integer_type
|
|
self.scale = scale
|
|
|
|
def pack(self,stream,value):
|
|
self.integer_type.pack(stream,int(value/self.scale))
|
|
|
|
def unpack(self,stream):
|
|
return self.integer_type.unpack(stream)*self.scale
|
|
|
|
def sizeof(self):
|
|
return self.integer_type.sizeof()
|
|
|
|
|
|
class ByteString:
|
|
|
|
def __init__(self,length):
|
|
self.length = length
|
|
|
|
def pack(self,stream,string):
|
|
if len(string) != self.length:
|
|
raise ValueError('wrong string length')
|
|
stream.write(string)
|
|
|
|
def unpack(self,stream):
|
|
return stream.read(self.length)
|
|
|
|
def sizeof(self):
|
|
return self.length
|
|
|
|
|
|
class Array:
|
|
|
|
def __init__(self,element_type,length):
|
|
self.element_type = element_type
|
|
self.length = length
|
|
|
|
def pack(self,stream,array):
|
|
if len(array) != self.length:
|
|
raise ValueError('wrong array length')
|
|
for value in array:
|
|
self.element_type.pack(stream,value)
|
|
|
|
def unpack(self,stream):
|
|
return [self.element_type.unpack(stream) for i in range(self.length)]
|
|
|
|
def sizeof(self):
|
|
return self.length*self.element_type.sizeof()
|
|
|
|
|
|
class CString:
|
|
|
|
def __init__(self,encoding):
|
|
self.encoding = encoding
|
|
|
|
def pack(self,stream,string):
|
|
stream.write((string + '\0').encode(self.encoding))
|
|
|
|
def unpack(self,stream):
|
|
#XXX: This might not work for all encodings
|
|
null = '\0'.encode(self.encoding)
|
|
string = b''
|
|
while True:
|
|
c = stream.read(len(null))
|
|
if c == null: break
|
|
string += c
|
|
return string.decode(self.encoding)
|
|
|
|
def sizeof(self):
|
|
return None
|
|
|
|
|
|
class PString:
|
|
|
|
def __init__(self,length_type,encoding):
|
|
self.length_type = length_type
|
|
self.encoding = encoding
|
|
|
|
def pack(self,stream,string):
|
|
string = string.encode(self.encoding)
|
|
self.length_type.pack(stream,len(string))
|
|
stream.write(string)
|
|
|
|
def unpack(self,stream):
|
|
length = self.length_type.unpack(stream)
|
|
return stream.read(length).decode(self.encoding)
|
|
|
|
def sizeof(self):
|
|
return None
|
|
|
|
|
|
class Field:
|
|
|
|
def __init__(self,name,field_type):
|
|
self.name = name
|
|
self.field_type = field_type
|
|
|
|
def pack(self,stream,struct):
|
|
self.field_type.pack(stream,getattr(struct,self.name))
|
|
|
|
def unpack(self,stream,struct):
|
|
setattr(struct,self.name,self.field_type.unpack(stream))
|
|
|
|
def sizeof(self):
|
|
return self.field_type.sizeof()
|
|
|
|
def equal(self,struct,other):
|
|
return getattr(struct,self.name) == getattr(other,self.name)
|
|
|
|
|
|
class Padding:
|
|
|
|
def __init__(self,length,padding=b'\xFF'):
|
|
self.length = length
|
|
self.padding = padding
|
|
|
|
def pack(self,stream,struct):
|
|
stream.write(self.padding*self.length)
|
|
|
|
def unpack(self,stream,struct):
|
|
stream.read(self.length)
|
|
|
|
def sizeof(self):
|
|
return self.length
|
|
|
|
def equal(self,struct,other):
|
|
return True
|
|
|
|
|
|
class StructClassDictionary(dict):
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.struct_fields = []
|
|
|
|
def __setitem__(self,key,value):
|
|
if not key[:2] == key[-2:] == '__' and not hasattr(value,'__get__'):
|
|
self.struct_fields.append(Field(key,value))
|
|
elif key == '__padding__':
|
|
self.struct_fields.append(value)
|
|
else:
|
|
super().__setitem__(key,value)
|
|
|
|
|
|
class StructMetaClass(type):
|
|
|
|
@classmethod
|
|
def __prepare__(metacls,cls,bases):
|
|
return StructClassDictionary()
|
|
|
|
def __new__(metacls,cls,bases,classdict):
|
|
if any(field.sizeof() is None for field in classdict.struct_fields):
|
|
struct_size = None
|
|
else:
|
|
struct_size = sum(field.sizeof() for field in classdict.struct_fields)
|
|
|
|
struct_class = type.__new__(metacls,cls,bases,classdict)
|
|
struct_class.struct_fields = classdict.struct_fields
|
|
struct_class.struct_size = struct_size
|
|
return struct_class
|
|
|
|
def __init__(self,cls,bases,classdict):
|
|
super().__init__(cls,bases,classdict)
|
|
|
|
|
|
class Struct(metaclass=StructMetaClass):
|
|
|
|
__slots__ = tuple()
|
|
|
|
def __eq__(self,other):
|
|
return all(field.equal(self,other) for field in self.struct_fields)
|
|
|
|
@classmethod
|
|
def pack(cls,stream,struct):
|
|
for field in cls.struct_fields:
|
|
field.pack(stream,struct)
|
|
|
|
@classmethod
|
|
def unpack(cls,stream):
|
|
struct = cls.__new__(cls) #TODO: what if __init__ does something important?
|
|
for field in cls.struct_fields:
|
|
field.unpack(stream,struct)
|
|
return struct
|
|
|
|
@classmethod
|
|
def sizeof(cls):
|
|
return cls.struct_size
|
|
|