Lorenzo Colitti | 8e269b2 | 2014-12-17 15:14:56 +0900 | [diff] [blame] | 1 | # 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 Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 15 | """A simple module for declaring C-like structures. |
| 16 | |
| 17 | Example usage: |
| 18 | |
| 19 | >>> # Declare a struct type by specifying name, field formats and field names. |
Lorenzo Colitti | d56cf27 | 2016-12-22 14:42:35 +0900 | [diff] [blame] | 20 | ... # Field formats are the same as those used in the struct module, except: |
| 21 | ... # - S: Nested Struct. |
Lorenzo Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 22 | ... # - A: NULL-padded ASCII string. Like s, but printing ignores contiguous |
| 23 | ... # trailing NULL blocks at the end. |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 24 | ... import cstruct |
| 25 | >>> NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid") |
| 26 | >>> |
| 27 | >>> |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 28 | >>> # Create instances from a tuple of values, raw bytes, zero-initialized, or |
| 29 | >>> # using keywords. |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 30 | ... n1 = NLMsgHdr((44, 32, 0x2, 0, 491)) |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 31 | >>> print(n1) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 32 | NLMsgHdr(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 Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 36 | >>> print(n2) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 37 | NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510) |
| 38 | >>> |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 39 | >>> n3 = netlink.NLMsgHdr() # Zero-initialized |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 40 | >>> print(n3) |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 41 | NLMsgHdr(length=0, type=0, flags=0, seq=0, pid=0) |
| 42 | >>> |
| 43 | >>> n4 = netlink.NLMsgHdr(length=44, type=33) # Other fields zero-initialized |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 44 | >>> print(n4) |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 45 | NLMsgHdr(length=44, type=33, flags=0, seq=0, pid=0) |
| 46 | >>> |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 47 | >>> # Serialize to raw bytes. |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 48 | ... print(n1.Pack().encode("hex")) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 49 | 2c0000002000020000000000eb010000 |
| 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 Colitti | d56cf27 | 2016-12-22 14:42:35 +0900 | [diff] [blame] | 59 | >>> # 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 Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 68 | """ |
| 69 | |
Lorenzo Colitti | 1daed72 | 2014-05-07 11:29:36 +0900 | [diff] [blame] | 70 | import ctypes |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 71 | import string |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 72 | import struct |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 73 | import re |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 74 | |
| 75 | |
Lorenzo Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 76 | def CalcSize(fmt): |
| 77 | if "A" in fmt: |
| 78 | fmt = fmt.replace("A", "s") |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 79 | # Remove the last digital since it will cause error in python3. |
| 80 | fmt = (re.split('\d+$', fmt)[0]) |
Lorenzo Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 81 | return struct.calcsize(fmt) |
| 82 | |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 83 | def CalcNumElements(fmt): |
Lorenzo Colitti | d56cf27 | 2016-12-22 14:42:35 +0900 | [diff] [blame] | 84 | prevlen = len(fmt) |
| 85 | fmt = fmt.replace("S", "") |
| 86 | numstructs = prevlen - len(fmt) |
Lorenzo Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 87 | size = CalcSize(fmt) |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 88 | elements = struct.unpack(fmt, b"\x00" * size) |
Lorenzo Colitti | d56cf27 | 2016-12-22 14:42:35 +0900 | [diff] [blame] | 89 | return len(elements) + numstructs |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 90 | |
| 91 | |
Lorenzo Colitti | f371329 | 2016-01-15 02:00:05 +0900 | [diff] [blame] | 92 | def Struct(name, fmt, fieldnames, substructs={}): |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 93 | """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 Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 109 | # Name of the struct. |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 110 | _name = name |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 111 | # List of field names. |
Lorenzo Colitti | f371329 | 2016-01-15 02:00:05 +0900 | [diff] [blame] | 112 | _fieldnames = fieldnames |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 113 | # Dict mapping field indices to nested struct classes. |
| 114 | _nested = {} |
Lorenzo Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 115 | # List of string fields that are ASCII strings. |
| 116 | _asciiz = set() |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 117 | |
Chenbo Feng | 9c67278 | 2017-06-08 16:28:13 -0700 | [diff] [blame] | 118 | _fieldnames = _fieldnames.split(" ") |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 119 | |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 120 | # 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 Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 124 | for i in range(len(fmt)): |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 125 | 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 Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 131 | elif fmt[i] == "A": |
| 132 | # Null-terminated ASCII string. |
| 133 | index = CalcNumElements(fmt[:i]) |
| 134 | _asciiz.add(index) |
| 135 | _format += "s" |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 136 | else: |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 137 | # Standard struct format character. |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 138 | _format += fmt[i] |
| 139 | |
Lorenzo Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 140 | _length = CalcSize(_format) |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 141 | |
Chenbo Feng | 9c67278 | 2017-06-08 16:28:13 -0700 | [diff] [blame] | 142 | offset_list = [0] |
| 143 | last_offset = 0 |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 144 | for i in range(len(_format)): |
Chenbo Feng | 9c67278 | 2017-06-08 16:28:13 -0700 | [diff] [blame] | 145 | 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 Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 151 | _offsets = dict(list(zip(_fieldnames, offset_list))) |
Chenbo Feng | 9c67278 | 2017-06-08 16:28:13 -0700 | [diff] [blame] | 152 | |
Lorenzo Colitti | a8df3f5 | 2017-12-09 19:03:28 +0900 | [diff] [blame] | 153 | # Check that the number of field names matches the number of fields. |
Nelson Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 154 | numfields = len(struct.unpack(_format, b"\x00" * _length)) |
Lorenzo Colitti | a8df3f5 | 2017-12-09 19:03:28 +0900 | [diff] [blame] | 155 | if len(_fieldnames) != numfields: |
| 156 | raise ValueError("Invalid cstruct: \"%s\" has %d elements, \"%s\" has %d." |
| 157 | % (fmt, numfields, fieldnames, len(_fieldnames))) |
| 158 | |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 159 | def _SetValues(self, values): |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 160 | # Replace self._values with the given list. We can't do direct assignment |
| 161 | # because of the __setattr__ overload on this class. |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 162 | 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 Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 167 | for index, value in enumerate(values): |
| 168 | if isinstance(value, str) and index in self._nested: |
| 169 | values[index] = self._nested[index](value) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 170 | self._SetValues(values) |
| 171 | |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 172 | 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 Li | b7769d6 | 2020-06-11 15:06:38 +0800 | [diff] [blame^] | 188 | for k, v in kwargs.items(): |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 189 | 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 Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 193 | raise TypeError("%s requires string of length %d, got %d" % |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 194 | (self._name, self._length, len(tuple_or_bytes))) |
| 195 | self._Parse(tuple_or_bytes) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 196 | else: |
| 197 | # Initializing from a tuple. |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 198 | if len(tuple_or_bytes) != len(self._fieldnames): |
Lorenzo Colitti | f371329 | 2016-01-15 02:00:05 +0900 | [diff] [blame] | 199 | raise TypeError("%s has exactly %d fieldnames (%d given)" % |
Jonathan Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 200 | (self._name, len(self._fieldnames), |
| 201 | len(tuple_or_bytes))) |
| 202 | self._SetValues(tuple_or_bytes) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 203 | |
| 204 | def _FieldIndex(self, attr): |
| 205 | try: |
Lorenzo Colitti | f371329 | 2016-01-15 02:00:05 +0900 | [diff] [blame] | 206 | return self._fieldnames.index(attr) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 207 | 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 Basseri | e7777d8 | 2017-05-26 15:27:52 -0700 | [diff] [blame] | 215 | # TODO: check value type against self._format and throw here, or else |
| 216 | # callers get an unhelpful exception when they call Pack(). |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 217 | self._values[self._FieldIndex(name)] = value |
| 218 | |
Chenbo Feng | 9c67278 | 2017-06-08 16:28:13 -0700 | [diff] [blame] | 219 | def offset(self, name): |
| 220 | if "." in name: |
| 221 | raise NotImplementedError("offset() on nested field") |
| 222 | return self._offsets[name] |
| 223 | |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 224 | @classmethod |
| 225 | def __len__(cls): |
| 226 | return cls._length |
| 227 | |
Lorenzo Colitti | f371329 | 2016-01-15 02:00:05 +0900 | [diff] [blame] | 228 | 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 Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 237 | @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 Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 244 | def Pack(self): |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 245 | values = [self._MaybePackStruct(v) for v in self._values] |
| 246 | return struct.pack(self._format, *values) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 247 | |
| 248 | def __str__(self): |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 249 | def FieldDesc(index, name, value): |
Lorenzo Colitti | c8f04cf | 2017-02-21 23:35:25 +0900 | [diff] [blame] | 250 | 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 Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 255 | return "%s=%s" % (name, value) |
| 256 | |
| 257 | descriptions = [ |
Lorenzo Colitti | f371329 | 2016-01-15 02:00:05 +0900 | [diff] [blame] | 258 | FieldDesc(i, n, v) for i, (n, v) in |
| 259 | enumerate(zip(self._fieldnames, self._values))] |
Lorenzo Colitti | 628ec67 | 2015-10-29 23:47:31 +0900 | [diff] [blame] | 260 | |
| 261 | return "%s(%s)" % (self._name, ", ".join(descriptions)) |
Lorenzo Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 262 | |
| 263 | def __repr__(self): |
| 264 | return str(self) |
| 265 | |
Lorenzo Colitti | 1daed72 | 2014-05-07 11:29:36 +0900 | [diff] [blame] | 266 | 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 Colitti | 369197d | 2014-04-04 20:18:08 +0900 | [diff] [blame] | 273 | return CStruct |
| 274 | |
| 275 | |
| 276 | def Read(data, struct_type): |
| 277 | length = len(struct_type) |
| 278 | return struct_type(data), data[length:] |