blob: 36de22a602c9a6a807e03028ef9c8a0ab2f063a3 [file] [log] [blame]
"""A simple module for declaring C-like structures.
Example usage:
>>> # Declare a struct type by specifying name, field formats and field names.
... # Field formats are the same as those used in the struct module.
... import cstruct
>>> NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
>>>
>>>
>>> # Create instances from tuples or raw bytes. Data past the end is ignored.
... n1 = NLMsgHdr((44, 32, 0x2, 0, 491))
>>> print n1
NLMsgHdr(length=44, type=32, flags=2, seq=0, pid=491)
>>>
>>> n2 = NLMsgHdr("\x2c\x00\x00\x00\x21\x00\x02\x00"
... "\x00\x00\x00\x00\xfe\x01\x00\x00" + "junk at end")
>>> print n2
NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510)
>>>
>>> # Serialize to raw bytes.
... print n1.Pack().encode("hex")
2c0000002000020000000000eb010000
>>>
>>> # Parse the beginning of a byte stream as a struct, and return the struct
... # and the remainder of the stream for further reading.
... data = ("\x2c\x00\x00\x00\x21\x00\x02\x00"
... "\x00\x00\x00\x00\xfe\x01\x00\x00"
... "more data")
>>> cstruct.Read(data, NLMsgHdr)
(NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510), 'more data')
>>>
"""
import ctypes
import struct
def Struct(name, fmt, fields):
"""Function that returns struct classes."""
class Meta(type):
def __len__(cls):
return cls._length
def __init__(cls, unused_name, unused_bases, namespace):
# Make the class object have the name that's passed in.
type.__init__(cls, namespace["_name"], unused_bases, namespace)
class CStruct(object):
"""Class representing a C-like structure."""
__metaclass__ = Meta
_name = name
_format = fmt
_fields = fields
_length = struct.calcsize(_format)
if isinstance(_fields, str):
_fields = _fields.split(" ")
def _SetValues(self, values):
super(CStruct, self).__setattr__("_values", list(values))
def _Parse(self, data):
data = data[:self._length]
values = list(struct.unpack(self._format, data))
self._SetValues(values)
def __init__(self, values):
# Initializing from a string.
if isinstance(values, str):
if len(values) < self._length:
raise TypeError("%s requires string of length %d, got %d" %
(self._name, self._length, len(values)))
self._Parse(values)
else:
# Initializing from a tuple.
if len(values) != len(self._fields):
raise TypeError("%s has exactly %d fields (%d given)" %
(self._name, len(self._fields), len(values)))
self._SetValues(values)
def _FieldIndex(self, attr):
try:
return self._fields.index(attr)
except ValueError:
raise AttributeError("'%s' has no attribute '%s'" %
(self._name, attr))
def __getattr__(self, name):
return self._values[self._FieldIndex(name)]
def __setattr__(self, name, value):
self._values[self._FieldIndex(name)] = value
@classmethod
def __len__(cls):
return cls._length
def Pack(self):
return struct.pack(self._format, *self._values)
def __str__(self):
return "%s(%s)" % (self._name, ", ".join(
"%s=%s" % (i, v) for i, v in zip(self._fields, self._values)))
def __repr__(self):
return str(self)
def CPointer(self):
"""Returns a C pointer to the serialized structure."""
buf = ctypes.create_string_buffer(self.Pack())
# Store the C buffer in the object so it doesn't get garbage collected.
super(CStruct, self).__setattr__("_buffer", buf)
return ctypes.addressof(self._buffer)
return CStruct
def Read(data, struct_type):
length = len(struct_type)
return struct_type(data), data[length:]