blob: c675c9ec65f43c1ced5e0003ab5afbc46856c1cb [file] [log] [blame]
Lorenzo Colitti8e269b22014-12-17 15:14:56 +09001# Copyright 2014 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Lorenzo Colitti369197d2014-04-04 20:18:08 +090015"""A simple module for declaring C-like structures.
16
17Example usage:
18
19>>> # Declare a struct type by specifying name, field formats and field names.
Lorenzo Colittid56cf272016-12-22 14:42:35 +090020... # Field formats are the same as those used in the struct module, except:
21... # - S: Nested Struct.
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +090022... # - A: NULL-padded ASCII string. Like s, but printing ignores contiguous
23... # trailing NULL blocks at the end.
Lorenzo Colitti369197d2014-04-04 20:18:08 +090024... import cstruct
25>>> NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
26>>>
27>>>
Jonathan Basserie7777d82017-05-26 15:27:52 -070028>>> # Create instances from a tuple of values, raw bytes, zero-initialized, or
29>>> # using keywords.
Lorenzo Colitti369197d2014-04-04 20:18:08 +090030... n1 = NLMsgHdr((44, 32, 0x2, 0, 491))
Nelson Lib7769d62020-06-11 15:06:38 +080031>>> print(n1)
Lorenzo Colitti369197d2014-04-04 20:18:08 +090032NLMsgHdr(length=44, type=32, flags=2, seq=0, pid=491)
33>>>
34>>> n2 = NLMsgHdr("\x2c\x00\x00\x00\x21\x00\x02\x00"
35... "\x00\x00\x00\x00\xfe\x01\x00\x00" + "junk at end")
Nelson Lib7769d62020-06-11 15:06:38 +080036>>> print(n2)
Lorenzo Colitti369197d2014-04-04 20:18:08 +090037NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510)
38>>>
Jonathan Basserie7777d82017-05-26 15:27:52 -070039>>> n3 = netlink.NLMsgHdr() # Zero-initialized
Nelson Lib7769d62020-06-11 15:06:38 +080040>>> print(n3)
Jonathan Basserie7777d82017-05-26 15:27:52 -070041NLMsgHdr(length=0, type=0, flags=0, seq=0, pid=0)
42>>>
43>>> n4 = netlink.NLMsgHdr(length=44, type=33) # Other fields zero-initialized
Nelson Lib7769d62020-06-11 15:06:38 +080044>>> print(n4)
Jonathan Basserie7777d82017-05-26 15:27:52 -070045NLMsgHdr(length=44, type=33, flags=0, seq=0, pid=0)
46>>>
Lorenzo Colitti369197d2014-04-04 20:18:08 +090047>>> # Serialize to raw bytes.
Nelson Lib7769d62020-06-11 15:06:38 +080048... print(n1.Pack().encode("hex"))
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900492c0000002000020000000000eb010000
50>>>
51>>> # Parse the beginning of a byte stream as a struct, and return the struct
52... # and the remainder of the stream for further reading.
53... data = ("\x2c\x00\x00\x00\x21\x00\x02\x00"
54... "\x00\x00\x00\x00\xfe\x01\x00\x00"
55... "more data")
56>>> cstruct.Read(data, NLMsgHdr)
57(NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510), 'more data')
58>>>
Lorenzo Colittid56cf272016-12-22 14:42:35 +090059>>> # Structs can contain one or more nested structs. The nested struct types
60... # are specified in a list as an optional last argument. Nested structs may
61... # contain nested structs.
62... S = cstruct.Struct("S", "=BI", "byte1 int2")
63>>> N = cstruct.Struct("N", "!BSiS", "byte1 s2 int3 s2", [S, S])
64>>> NN = cstruct.Struct("NN", "SHS", "s1 word2 n3", [S, N])
65>>> nn = NN((S((1, 25000)), -29876, N((55, S((5, 6)), 1111, S((7, 8))))))
66>>> nn.n3.s2.int2 = 5
67>>>
Lorenzo Colitti369197d2014-04-04 20:18:08 +090068"""
69
Lorenzo Colitti1daed722014-05-07 11:29:36 +090070import ctypes
Lorenzo Colitti628ec672015-10-29 23:47:31 +090071import string
Lorenzo Colitti369197d2014-04-04 20:18:08 +090072import struct
Nelson Lib7769d62020-06-11 15:06:38 +080073import re
Lorenzo Colitti369197d2014-04-04 20:18:08 +090074
75
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +090076def CalcSize(fmt):
77 if "A" in fmt:
78 fmt = fmt.replace("A", "s")
Nelson Lib7769d62020-06-11 15:06:38 +080079 # Remove the last digital since it will cause error in python3.
80 fmt = (re.split('\d+$', fmt)[0])
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +090081 return struct.calcsize(fmt)
82
Lorenzo Colitti628ec672015-10-29 23:47:31 +090083def CalcNumElements(fmt):
Lorenzo Colittid56cf272016-12-22 14:42:35 +090084 prevlen = len(fmt)
85 fmt = fmt.replace("S", "")
86 numstructs = prevlen - len(fmt)
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +090087 size = CalcSize(fmt)
Nelson Lib7769d62020-06-11 15:06:38 +080088 elements = struct.unpack(fmt, b"\x00" * size)
Lorenzo Colittid56cf272016-12-22 14:42:35 +090089 return len(elements) + numstructs
Lorenzo Colitti628ec672015-10-29 23:47:31 +090090
91
Lorenzo Colittif3713292016-01-15 02:00:05 +090092def Struct(name, fmt, fieldnames, substructs={}):
Lorenzo Colitti369197d2014-04-04 20:18:08 +090093 """Function that returns struct classes."""
94
95 class Meta(type):
96
97 def __len__(cls):
98 return cls._length
99
100 def __init__(cls, unused_name, unused_bases, namespace):
101 # Make the class object have the name that's passed in.
102 type.__init__(cls, namespace["_name"], unused_bases, namespace)
103
104 class CStruct(object):
105 """Class representing a C-like structure."""
106
107 __metaclass__ = Meta
108
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900109 # Name of the struct.
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900110 _name = name
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900111 # List of field names.
Lorenzo Colittif3713292016-01-15 02:00:05 +0900112 _fieldnames = fieldnames
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900113 # Dict mapping field indices to nested struct classes.
114 _nested = {}
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +0900115 # List of string fields that are ASCII strings.
116 _asciiz = set()
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900117
Chenbo Feng9c672782017-06-08 16:28:13 -0700118 _fieldnames = _fieldnames.split(" ")
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900119
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900120 # Parse fmt into _format, converting any S format characters to "XXs",
121 # where XX is the length of the struct type's packed representation.
122 _format = ""
123 laststructindex = 0
Nelson Lib7769d62020-06-11 15:06:38 +0800124 for i in range(len(fmt)):
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900125 if fmt[i] == "S":
126 # Nested struct. Record the index in our struct it should go into.
127 index = CalcNumElements(fmt[:i])
128 _nested[index] = substructs[laststructindex]
129 laststructindex += 1
130 _format += "%ds" % len(_nested[index])
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +0900131 elif fmt[i] == "A":
132 # Null-terminated ASCII string.
133 index = CalcNumElements(fmt[:i])
134 _asciiz.add(index)
135 _format += "s"
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900136 else:
Jonathan Basserie7777d82017-05-26 15:27:52 -0700137 # Standard struct format character.
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900138 _format += fmt[i]
139
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +0900140 _length = CalcSize(_format)
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900141
Chenbo Feng9c672782017-06-08 16:28:13 -0700142 offset_list = [0]
143 last_offset = 0
Nelson Lib7769d62020-06-11 15:06:38 +0800144 for i in range(len(_format)):
Chenbo Feng9c672782017-06-08 16:28:13 -0700145 offset = CalcSize(_format[:i])
146 if offset > last_offset:
147 last_offset = offset
148 offset_list.append(offset)
149
150 # A dictionary that maps field names to their offsets in the struct.
Nelson Lib7769d62020-06-11 15:06:38 +0800151 _offsets = dict(list(zip(_fieldnames, offset_list)))
Chenbo Feng9c672782017-06-08 16:28:13 -0700152
Lorenzo Colittia8df3f52017-12-09 19:03:28 +0900153 # Check that the number of field names matches the number of fields.
Nelson Lib7769d62020-06-11 15:06:38 +0800154 numfields = len(struct.unpack(_format, b"\x00" * _length))
Lorenzo Colittia8df3f52017-12-09 19:03:28 +0900155 if len(_fieldnames) != numfields:
156 raise ValueError("Invalid cstruct: \"%s\" has %d elements, \"%s\" has %d."
157 % (fmt, numfields, fieldnames, len(_fieldnames)))
158
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900159 def _SetValues(self, values):
Jonathan Basserie7777d82017-05-26 15:27:52 -0700160 # Replace self._values with the given list. We can't do direct assignment
161 # because of the __setattr__ overload on this class.
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900162 super(CStruct, self).__setattr__("_values", list(values))
163
164 def _Parse(self, data):
165 data = data[:self._length]
166 values = list(struct.unpack(self._format, data))
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900167 for index, value in enumerate(values):
168 if isinstance(value, str) and index in self._nested:
169 values[index] = self._nested[index](value)
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900170 self._SetValues(values)
171
Jonathan Basserie7777d82017-05-26 15:27:52 -0700172 def __init__(self, tuple_or_bytes=None, **kwargs):
173 """Construct an instance of this Struct.
174
175 1. With no args, the whole struct is zero-initialized.
176 2. With keyword args, the matching fields are populated; rest are zeroed.
177 3. With one tuple as the arg, the fields are assigned based on position.
178 4. With one string arg, the Struct is parsed from bytes.
179 """
180 if tuple_or_bytes and kwargs:
181 raise TypeError(
182 "%s: cannot specify both a tuple and keyword args" % self._name)
183
184 if tuple_or_bytes is None:
185 # Default construct from null bytes.
186 self._Parse("\x00" * len(self))
187 # If any keywords were supplied, set those fields.
Nelson Lib7769d62020-06-11 15:06:38 +0800188 for k, v in kwargs.items():
Jonathan Basserie7777d82017-05-26 15:27:52 -0700189 setattr(self, k, v)
190 elif isinstance(tuple_or_bytes, str):
191 # Initializing from a string.
192 if len(tuple_or_bytes) < self._length:
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900193 raise TypeError("%s requires string of length %d, got %d" %
Jonathan Basserie7777d82017-05-26 15:27:52 -0700194 (self._name, self._length, len(tuple_or_bytes)))
195 self._Parse(tuple_or_bytes)
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900196 else:
197 # Initializing from a tuple.
Jonathan Basserie7777d82017-05-26 15:27:52 -0700198 if len(tuple_or_bytes) != len(self._fieldnames):
Lorenzo Colittif3713292016-01-15 02:00:05 +0900199 raise TypeError("%s has exactly %d fieldnames (%d given)" %
Jonathan Basserie7777d82017-05-26 15:27:52 -0700200 (self._name, len(self._fieldnames),
201 len(tuple_or_bytes)))
202 self._SetValues(tuple_or_bytes)
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900203
204 def _FieldIndex(self, attr):
205 try:
Lorenzo Colittif3713292016-01-15 02:00:05 +0900206 return self._fieldnames.index(attr)
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900207 except ValueError:
208 raise AttributeError("'%s' has no attribute '%s'" %
209 (self._name, attr))
210
211 def __getattr__(self, name):
212 return self._values[self._FieldIndex(name)]
213
214 def __setattr__(self, name, value):
Jonathan Basserie7777d82017-05-26 15:27:52 -0700215 # TODO: check value type against self._format and throw here, or else
216 # callers get an unhelpful exception when they call Pack().
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900217 self._values[self._FieldIndex(name)] = value
218
Chenbo Feng9c672782017-06-08 16:28:13 -0700219 def offset(self, name):
220 if "." in name:
221 raise NotImplementedError("offset() on nested field")
222 return self._offsets[name]
223
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900224 @classmethod
225 def __len__(cls):
226 return cls._length
227
Lorenzo Colittif3713292016-01-15 02:00:05 +0900228 def __ne__(self, other):
229 return not self.__eq__(other)
230
231 def __eq__(self, other):
232 return (isinstance(other, self.__class__) and
233 self._name == other._name and
234 self._fieldnames == other._fieldnames and
235 self._values == other._values)
236
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900237 @staticmethod
238 def _MaybePackStruct(value):
239 if hasattr(value, "__metaclass__"):# and value.__metaclass__ == Meta:
240 return value.Pack()
241 else:
242 return value
243
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900244 def Pack(self):
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900245 values = [self._MaybePackStruct(v) for v in self._values]
246 return struct.pack(self._format, *values)
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900247
248 def __str__(self):
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900249 def FieldDesc(index, name, value):
Lorenzo Colittic8f04cf2017-02-21 23:35:25 +0900250 if isinstance(value, str):
251 if index in self._asciiz:
252 value = value.rstrip("\x00")
253 elif any(c not in string.printable for c in value):
254 value = value.encode("hex")
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900255 return "%s=%s" % (name, value)
256
257 descriptions = [
Lorenzo Colittif3713292016-01-15 02:00:05 +0900258 FieldDesc(i, n, v) for i, (n, v) in
259 enumerate(zip(self._fieldnames, self._values))]
Lorenzo Colitti628ec672015-10-29 23:47:31 +0900260
261 return "%s(%s)" % (self._name, ", ".join(descriptions))
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900262
263 def __repr__(self):
264 return str(self)
265
Lorenzo Colitti1daed722014-05-07 11:29:36 +0900266 def CPointer(self):
267 """Returns a C pointer to the serialized structure."""
268 buf = ctypes.create_string_buffer(self.Pack())
269 # Store the C buffer in the object so it doesn't get garbage collected.
270 super(CStruct, self).__setattr__("_buffer", buf)
271 return ctypes.addressof(self._buffer)
272
Lorenzo Colitti369197d2014-04-04 20:18:08 +0900273 return CStruct
274
275
276def Read(data, struct_type):
277 length = len(struct_type)
278 return struct_type(data), data[length:]