blob: 71338ab0a602e01eef41e7a37a7de9774c12b108 [file] [log] [blame]
Harald Weltebb3b5df2021-05-24 23:15:54 +02001"""object-oriented TLV parser/encoder library."""
2
3# (C) 2021 by Harald Welte <laforge@osmocom.org>
4# All Rights Reserved
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20from typing import Optional, List, Dict, Any, Tuple
21from bidict import bidict
22from construct import *
23
24from pySim.utils import bertlv_encode_len, bertlv_parse_len, bertlv_encode_tag, bertlv_parse_tag
25from pySim.utils import comprehensiontlv_encode_tag, comprehensiontlv_parse_tag
26from pySim.utils import bertlv_parse_one, comprehensiontlv_parse_one
27from pySim.utils import bertlv_parse_tag_raw, comprehensiontlv_parse_tag_raw
28
29from pySim.construct import parse_construct, LV, HexAdapter, BcdAdapter, BitsRFU, GsmStringAdapter
30from pySim.exceptions import *
31
32import inspect
33import abc
34
Harald Weltec91085e2022-02-10 18:05:45 +010035
Harald Weltebb3b5df2021-05-24 23:15:54 +020036class TlvMeta(abc.ABCMeta):
37 """Metaclass which we use to set some class variables at the time of defining a subclass.
38 This allows us to create subclasses for each TLV/IE type, where the class represents fixed
39 parameters like the tag/type and instances of it represent the actual TLV data."""
40 def __new__(metacls, name, bases, namespace, **kwargs):
41 #print("TlvMeta_new_(metacls=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (metacls, name, bases, namespace, kwargs))
42 x = super().__new__(metacls, name, bases, namespace)
43 # this becomes a _class_ variable, not an instance variable
44 x.tag = namespace.get('tag', kwargs.get('tag', None))
45 x.desc = namespace.get('desc', kwargs.get('desc', None))
46 nested = namespace.get('nested', kwargs.get('nested', None))
47 if nested is None or inspect.isclass(nested) and issubclass(nested, TLV_IE_Collection):
48 # caller has specified TLV_IE_Collection sub-class, we can directly reference it
49 x.nested_collection_cls = nested
50 else:
51 # caller passed list of other TLV classes that might possibly appear within us,
52 # build a dynamically-created TLV_IE_Collection sub-class and reference it
53 name = 'auto_collection_%s' % (name)
54 cls = type(name, (TLV_IE_Collection,), {'nested': nested})
55 x.nested_collection_cls = cls
56 return x
57
Harald Weltec91085e2022-02-10 18:05:45 +010058
Harald Weltebb3b5df2021-05-24 23:15:54 +020059class TlvCollectionMeta(abc.ABCMeta):
60 """Metaclass which we use to set some class variables at the time of defining a subclass.
61 This allows us to create subclasses for each Collection type, where the class represents fixed
62 parameters like the nested IE classes and instances of it represent the actual TLV data."""
63 def __new__(metacls, name, bases, namespace, **kwargs):
64 #print("TlvCollectionMeta_new_(metacls=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (metacls, name, bases, namespace, kwargs))
65 x = super().__new__(metacls, name, bases, namespace)
66 # this becomes a _class_ variable, not an instance variable
67 x.possible_nested = namespace.get('nested', kwargs.get('nested', None))
68 return x
69
70
71class Transcodable(abc.ABC):
72 _construct = None
73 """Base class for something that can be encoded + encoded. Decoding and Encoding happens either
74 * via a 'construct' object stored in a derived class' _construct variable, or
75 * via a 'construct' object stored in an instance _construct variable, or
76 * via a derived class' _{to,from}_bytes() methods."""
Harald Weltec91085e2022-02-10 18:05:45 +010077
Harald Weltebb3b5df2021-05-24 23:15:54 +020078 def __init__(self):
79 self.encoded = None
80 self.decoded = None
81 self._construct = None
82
83 def to_bytes(self) -> bytes:
84 """Convert from internal representation to binary bytes. Store the binary result
85 in the internal state and return it."""
Harald Welte04c13022021-10-21 10:02:10 +020086 if not self.decoded:
87 do = b''
88 elif self._construct:
Harald Weltebb3b5df2021-05-24 23:15:54 +020089 do = self._construct.build(self.decoded, total_len=None)
90 elif self.__class__._construct:
91 do = self.__class__._construct.build(self.decoded, total_len=None)
92 else:
93 do = self._to_bytes()
94 self.encoded = do
95 return do
96
97 # not an abstractmethod, as it is only required if no _construct exists
98 def _to_bytes(self):
99 raise NotImplementedError
100
Harald Weltec91085e2022-02-10 18:05:45 +0100101 def from_bytes(self, do: bytes):
Harald Weltebb3b5df2021-05-24 23:15:54 +0200102 """Convert from binary bytes to internal representation. Store the decoded result
103 in the internal state and return it."""
104 self.encoded = do
Harald Welte04c13022021-10-21 10:02:10 +0200105 if self.encoded == b'':
106 self.decoded = None
107 elif self._construct:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200108 self.decoded = parse_construct(self._construct, do)
109 elif self.__class__._construct:
110 self.decoded = parse_construct(self.__class__._construct, do)
111 else:
112 self.decoded = self._from_bytes(do)
113 return self.decoded
114
115 # not an abstractmethod, as it is only required if no _construct exists
Harald Weltec91085e2022-02-10 18:05:45 +0100116 def _from_bytes(self, do: bytes):
Harald Weltebb3b5df2021-05-24 23:15:54 +0200117 raise NotImplementedError
118
Harald Weltec91085e2022-02-10 18:05:45 +0100119
Harald Weltebb3b5df2021-05-24 23:15:54 +0200120class IE(Transcodable, metaclass=TlvMeta):
121 # we specify the metaclass so any downstream subclasses will automatically use it
122 """Base class for various Information Elements. We understand the notion of a hierarchy
123 of IEs on top of the Transcodable class."""
124 # this is overridden by the TlvMeta metaclass, if it is used to create subclasses
125 nested_collection_cls = None
126 tag = None
127
128 def __init__(self, **kwargs):
129 super().__init__()
130 self.nested_collection = None
131 if self.nested_collection_cls:
132 self.nested_collection = self.nested_collection_cls()
133 # if we are a constructed IE, [ordered] list of actual child-IE instances
134 self.children = kwargs.get('children', [])
135 self.decoded = kwargs.get('decoded', None)
136
137 def __repr__(self):
138 """Return a string representing the [nested] IE data (for print)."""
139 if len(self.children):
140 member_strs = [repr(x) for x in self.children]
141 return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
142 else:
143 return '%s(%s)' % (type(self).__name__, self.decoded)
144
145 def to_dict(self):
146 """Return a JSON-serializable dict representing the [nested] IE data."""
147 if len(self.children):
148 v = [x.to_dict() for x in self.children]
149 else:
150 v = self.decoded
151 return {type(self).__name__: v}
152
Harald Weltec91085e2022-02-10 18:05:45 +0100153 def from_dict(self, decoded: dict):
Harald Weltebb3b5df2021-05-24 23:15:54 +0200154 """Set the IE internal decoded representation to data from the argument.
155 If this is a nested IE, the child IE instance list is re-created."""
156 if self.nested_collection:
157 self.children = self.nested_collection.from_dict(decoded)
158 else:
159 self.children = []
160 self.decoded = decoded
161
162 def is_constructed(self):
163 """Is this IE constructed by further nested IEs?"""
164 if len(self.children):
165 return True
166 else:
167 return False
168
169 @abc.abstractmethod
170 def to_ie(self) -> bytes:
171 """Convert the internal representation to entire IE including IE header."""
172
173 def to_bytes(self) -> bytes:
174 """Convert the internal representation _of the value part_ to binary bytes."""
175 if self.is_constructed():
176 # concatenate the encoded IE of all children to form the value part
177 out = b''
178 for c in self.children:
179 out += c.to_ie()
180 return out
181 else:
182 return super().to_bytes()
183
Harald Weltec91085e2022-02-10 18:05:45 +0100184 def from_bytes(self, do: bytes):
Harald Weltebb3b5df2021-05-24 23:15:54 +0200185 """Parse _the value part_ from binary bytes to internal representation."""
186 if self.nested_collection:
187 self.children = self.nested_collection.from_bytes(do)
188 else:
189 self.children = []
190 return super().from_bytes(do)
191
192
193class TLV_IE(IE):
194 """Abstract base class for various TLV type Information Elements."""
Harald Weltec91085e2022-02-10 18:05:45 +0100195
Harald Weltebb3b5df2021-05-24 23:15:54 +0200196 def __init__(self, **kwargs):
197 super().__init__(**kwargs)
198
199 def _compute_tag(self) -> int:
200 """Compute the tag (sometimes the tag encodes part of the value)."""
201 return self.tag
202
203 @classmethod
204 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100205 def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200206 """Obtain the raw TAG at the start of the bytes provided by the user."""
207
208 @classmethod
209 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100210 def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200211 """Obtain the length encoded at the start of the bytes provided by the user."""
212
213 @abc.abstractmethod
214 def _encode_tag(self) -> bytes:
215 """Encode the tag part. Must be provided by derived (TLV format specific) class."""
216
217 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100218 def _encode_len(self, val: bytes) -> bytes:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200219 """Encode the length part assuming a certain binary value. Must be provided by
220 derived (TLV format specific) class."""
221
222 def to_ie(self):
223 return self.to_tlv()
224
225 def to_tlv(self):
226 """Convert the internal representation to binary TLV bytes."""
227 val = self.to_bytes()
228 return self._encode_tag() + self._encode_len(val) + val
229
Harald Weltec91085e2022-02-10 18:05:45 +0100230 def from_tlv(self, do: bytes):
Harald Weltebb3b5df2021-05-24 23:15:54 +0200231 (rawtag, remainder) = self.__class__._parse_tag_raw(do)
232 if rawtag:
233 if rawtag != self.tag:
234 raise ValueError("%s: Encountered tag %s doesn't match our supported tag %s" %
235 (self, rawtag, self.tag))
236 (length, remainder) = self.__class__._parse_len(remainder)
237 value = remainder[:length]
238 remainder = remainder[length:]
239 else:
240 value = do
241 remainder = b''
242 dec = self.from_bytes(value)
243 return dec, remainder
244
245
246class BER_TLV_IE(TLV_IE):
247 """TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
Harald Weltec91085e2022-02-10 18:05:45 +0100248
Harald Weltebb3b5df2021-05-24 23:15:54 +0200249 def __init__(self, **kwargs):
250 super().__init__(**kwargs)
251
252 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100253 def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200254 return bertlv_parse_tag(do)
255
256 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100257 def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200258 return bertlv_parse_tag_raw(do)
259
260 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100261 def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200262 return bertlv_parse_len(do)
263
264 def _encode_tag(self) -> bytes:
265 return bertlv_encode_tag(self._compute_tag())
266
Harald Weltec91085e2022-02-10 18:05:45 +0100267 def _encode_len(self, val: bytes) -> bytes:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200268 return bertlv_encode_len(len(val))
269
270
271class COMPR_TLV_IE(TLV_IE):
272 """TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
Harald Weltec91085e2022-02-10 18:05:45 +0100273
Harald Weltebb3b5df2021-05-24 23:15:54 +0200274 def __init__(self, **kwargs):
275 super().__init__(**kwargs)
276 self.comprehension = False
277
278 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100279 def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200280 return comprehensiontlv_parse_tag(do)
281
282 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100283 def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200284 return comprehensiontlv_parse_tag_raw(do)
285
286 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100287 def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200288 return bertlv_parse_len(do)
289
290 def _encode_tag(self) -> bytes:
291 return comprehensiontlv_encode_tag(self._compute_tag())
292
Harald Weltec91085e2022-02-10 18:05:45 +0100293 def _encode_len(self, val: bytes) -> bytes:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200294 return bertlv_encode_len(len(val))
295
296
297class TLV_IE_Collection(metaclass=TlvCollectionMeta):
298 # we specify the metaclass so any downstream subclasses will automatically use it
299 """A TLV_IE_Collection consists of multiple TLV_IE classes identified by their tags.
300 A given encoded DO may contain any of them in any order, and may contain multiple instances
301 of each DO."""
302 # this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
303 possible_nested = []
Harald Weltec91085e2022-02-10 18:05:45 +0100304
Harald Weltebb3b5df2021-05-24 23:15:54 +0200305 def __init__(self, desc=None, **kwargs):
306 self.desc = desc
307 #print("possible_nested: ", self.possible_nested)
308 self.members = kwargs.get('nested', self.possible_nested)
309 self.members_by_tag = {}
310 self.members_by_name = {}
Harald Weltec91085e2022-02-10 18:05:45 +0100311 self.members_by_tag = {m.tag: m for m in self.members}
312 self.members_by_name = {m.__name__: m for m in self.members}
Harald Weltebb3b5df2021-05-24 23:15:54 +0200313 # if we are a constructed IE, [ordered] list of actual child-IE instances
314 self.children = kwargs.get('children', [])
315 self.encoded = None
316
317 def __str__(self):
318 member_strs = [str(x) for x in self.members]
319 return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
320
321 def __repr__(self):
322 member_strs = [repr(x) for x in self.members]
323 return '%s(%s)' % (self.__class__, ','.join(member_strs))
324
325 def __add__(self, other):
326 """Extending TLV_IE_Collections with other TLV_IE_Collections or TLV_IEs."""
327 if isinstance(other, TLV_IE_Collection):
328 # adding one collection to another
329 members = self.members + other.members
330 return TLV_IE_Collection(self.desc, nested=members)
331 elif inspect.isclass(other) and issubclass(other, TLV_IE):
332 # adding a member to a collection
Harald Weltec91085e2022-02-10 18:05:45 +0100333 return TLV_IE_Collection(self.desc, nested=self.members + [other])
Harald Weltebb3b5df2021-05-24 23:15:54 +0200334 else:
335 raise TypeError
336
Harald Weltec91085e2022-02-10 18:05:45 +0100337 def from_bytes(self, binary: bytes) -> List[TLV_IE]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200338 """Create a list of TLV_IEs from the collection based on binary input data.
339 Args:
340 binary : binary bytes of encoded data
341 Returns:
342 list of instances of TLV_IE sub-classes containing parsed data
343 """
344 self.encoded = binary
345 # list of instances of TLV_IE collection member classes appearing in the data
346 res = []
347 remainder = binary
348 first = next(iter(self.members_by_tag.values()))
349 # iterate until no binary trailer is left
350 while len(remainder):
351 # obtain the tag at the start of the remainder
352 tag, r = first._parse_tag_raw(remainder)
Harald Weltefb506212021-05-29 21:28:24 +0200353 if tag == None:
354 return res
Harald Weltebb3b5df2021-05-24 23:15:54 +0200355 if tag in self.members_by_tag:
356 cls = self.members_by_tag[tag]
357 # create an instance and parse accordingly
358 inst = cls()
359 dec, remainder = inst.from_tlv(remainder)
360 res.append(inst)
361 else:
362 # unknown tag; create the related class on-the-fly using the same base class
363 name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
Harald Weltec91085e2022-02-10 18:05:45 +0100364 cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
365 'nested_collection_cls': None})
366 cls._from_bytes = lambda s, a: {'raw': a.hex()}
Harald Weltebb3b5df2021-05-24 23:15:54 +0200367 cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
368 # create an instance and parse accordingly
369 inst = cls()
370 dec, remainder = inst.from_tlv(remainder)
371 res.append(inst)
372 self.children = res
373 return res
374
Harald Weltec91085e2022-02-10 18:05:45 +0100375 def from_dict(self, decoded: List[dict]) -> List[TLV_IE]:
Harald Weltebb3b5df2021-05-24 23:15:54 +0200376 """Create a list of TLV_IE instances from the collection based on an array
377 of dicts, where they key indicates the name of the TLV_IE subclass to use."""
378 # list of instances of TLV_IE collection member classes appearing in the data
379 res = []
380 for i in decoded:
381 for k in i.keys():
382 if k in self.members_by_name:
383 cls = self.members_by_name[k]
Harald Welte58953802021-10-21 11:33:44 +0200384 inst = cls()
385 inst.from_dict(i[k])
Harald Weltebb3b5df2021-05-24 23:15:54 +0200386 res.append(inst)
387 else:
388 raise ValueError('%s: Unknown TLV Class %s in %s; expected %s' %
389 (self, i[0], decoded, self.members_by_name.keys()))
390 self.children = res
391 return res
392
393 def to_dict(self):
394 return [x.to_dict() for x in self.children]
395
396 def to_bytes(self):
397 out = b''
398 for c in self.children:
399 out += c.to_tlv()
400 return out
401
402 def from_tlv(self, do):
403 return self.from_bytes(do)
404
405 def to_tlv(self):
406 return self.to_bytes()
Harald Welte9a2a6692022-02-11 15:44:28 +0100407
408
409def flatten_dict_lists(inp):
410 """hierarchically flatten each list-of-dicts into a single dict. This is useful to
411 make the output of hierarchical TLV decoder structures flatter and more easy to read."""
412 def are_all_elements_dict(l):
413 for e in l:
414 if not isinstance(e, dict):
415 return False
416 return True
417
418 if isinstance(inp, list):
419 if are_all_elements_dict(inp):
420 # flatten into one shared dict
421 newdict = {}
422 for e in inp:
423 key = list(e.keys())[0]
424 newdict[key] = e[key]
425 inp = newdict
426 # process result as any native dict
427 return {k:flatten_dict_lists(inp[k]) for k in inp.keys()}
428 else:
429 return [flatten_dict_lists(x) for x in inp]
430 elif isinstance(inp, dict):
431 return {k:flatten_dict_lists(inp[k]) for k in inp.keys()}
432 else:
433 return inp