Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 1 | """A simple module for declaring C-like structures. |
| 2 | |
| 3 | Example 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 |
| 14 | NLMsgHdr(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 |
| 19 | NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510) |
| 20 | >>> |
| 21 | >>> # Serialize to raw bytes. |
| 22 | ... print n1.Pack().encode("hex") |
| 23 | 2c0000002000020000000000eb010000 |
| 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 Colitti | 1daed72 | 2014-05-07 11:29:36 +0900 | [diff] [blame^] | 35 | import ctypes |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 36 | import struct |
| 37 | |
| 38 | |
| 39 | def 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 Colitti | 1daed72 | 2014-05-07 11:29:36 +0900 | [diff] [blame^] | 113 | 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 Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 120 | return CStruct |
| 121 | |
| 122 | |
| 123 | def Read(data, struct_type): |
| 124 | length = len(struct_type) |
| 125 | return struct_type(data), data[length:] |