blob: 36de22a602c9a6a807e03028ef9c8a0ab2f063a3 [file] [log] [blame]
Lorenzo Colitti369197d2014-04-04 20:18:08 +09001"""A simple module for declaring C-like structures.
2
3Example usage:
4
5>>> # Declare a struct type by specifying name, field formats and field names.
6... # Field formats are the same as those used in the struct module.
7... import cstruct
8>>> NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
9>>>
10>>>
11>>> # Create instances from tuples or raw bytes. Data past the end is ignored.
12... n1 = NLMsgHdr((44, 32, 0x2, 0, 491))
13>>> print n1
14NLMsgHdr(length=44, type=32, flags=2, seq=0, pid=491)
15>>>
16>>> n2 = NLMsgHdr("\x2c\x00\x00\x00\x21\x00\x02\x00"
17... "\x00\x00\x00\x00\xfe\x01\x00\x00" + "junk at end")
18>>> print n2
19NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510)
20>>>
21>>> # Serialize to raw bytes.
22... print n1.Pack().encode("hex")
232c0000002000020000000000eb010000
24>>>
25>>> # Parse the beginning of a byte stream as a struct, and return the struct
26... # and the remainder of the stream for further reading.
27... data = ("\x2c\x00\x00\x00\x21\x00\x02\x00"
28... "\x00\x00\x00\x00\xfe\x01\x00\x00"
29... "more data")
30>>> cstruct.Read(data, NLMsgHdr)
31(NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510), 'more data')
32>>>
33"""
34
Lorenzo Colitti1daed722014-05-07 11:29:36 +090035import ctypes
Lorenzo Colitti369197d2014-04-04 20:18:08 +090036import struct
37
38
39def Struct(name, fmt, fields):
40 """Function that returns struct classes."""
41
42 class Meta(type):
43
44 def __len__(cls):
45 return cls._length
46
47 def __init__(cls, unused_name, unused_bases, namespace):
48 # Make the class object have the name that's passed in.
49 type.__init__(cls, namespace["_name"], unused_bases, namespace)
50
51 class CStruct(object):
52 """Class representing a C-like structure."""
53
54 __metaclass__ = Meta
55
56 _name = name
57 _format = fmt
58 _fields = fields
59
60 _length = struct.calcsize(_format)
61 if isinstance(_fields, str):
62 _fields = _fields.split(" ")
63
64 def _SetValues(self, values):
65 super(CStruct, self).__setattr__("_values", list(values))
66
67 def _Parse(self, data):
68 data = data[:self._length]
69 values = list(struct.unpack(self._format, data))
70 self._SetValues(values)
71
72 def __init__(self, values):
73 # Initializing from a string.
74 if isinstance(values, str):
75 if len(values) < self._length:
76 raise TypeError("%s requires string of length %d, got %d" %
77 (self._name, self._length, len(values)))
78 self._Parse(values)
79 else:
80 # Initializing from a tuple.
81 if len(values) != len(self._fields):
82 raise TypeError("%s has exactly %d fields (%d given)" %
83 (self._name, len(self._fields), len(values)))
84 self._SetValues(values)
85
86 def _FieldIndex(self, attr):
87 try:
88 return self._fields.index(attr)
89 except ValueError:
90 raise AttributeError("'%s' has no attribute '%s'" %
91 (self._name, attr))
92
93 def __getattr__(self, name):
94 return self._values[self._FieldIndex(name)]
95
96 def __setattr__(self, name, value):
97 self._values[self._FieldIndex(name)] = value
98
99 @classmethod
100 def __len__(cls):
101 return cls._length
102
103 def Pack(self):
104 return struct.pack(self._format, *self._values)
105
106 def __str__(self):
107 return "%s(%s)" % (self._name, ", ".join(
108 "%s=%s" % (i, v) for i, v in zip(self._fields, self._values)))
109
110 def __repr__(self):
111 return str(self)
112
Lorenzo Colitti1daed722014-05-07 11:29:36 +0900113 def CPointer(self):
114 """Returns a C pointer to the serialized structure."""
115 buf = ctypes.create_string_buffer(self.Pack())
116 # Store the C buffer in the object so it doesn't get garbage collected.
117 super(CStruct, self).__setattr__("_buffer", buf)
118 return ctypes.addressof(self._buffer)
119
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900120 return CStruct
121
122
123def Read(data, struct_type):
124 length = len(struct_type)
125 return struct_type(data), data[length:]