2021-05-24 21:15:54 +00:00
|
|
|
"""object-oriented TLV parser/encoder library."""
|
|
|
|
|
|
|
|
# (C) 2021 by Harald Welte <laforge@osmocom.org>
|
|
|
|
# All Rights Reserved
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2024-02-05 00:11:57 +00:00
|
|
|
import inspect
|
|
|
|
import abc
|
|
|
|
import re
|
|
|
|
from typing import List, Tuple
|
2021-05-24 21:15:54 +00:00
|
|
|
|
|
|
|
from pySim.utils import bertlv_encode_len, bertlv_parse_len, bertlv_encode_tag, bertlv_parse_tag
|
|
|
|
from pySim.utils import comprehensiontlv_encode_tag, comprehensiontlv_parse_tag
|
|
|
|
from pySim.utils import bertlv_parse_tag_raw, comprehensiontlv_parse_tag_raw
|
2024-01-14 09:09:07 +00:00
|
|
|
from pySim.utils import dgi_parse_tag_raw, dgi_parse_len, dgi_encode_tag, dgi_encode_len
|
2021-05-24 21:15:54 +00:00
|
|
|
|
2024-02-05 00:11:57 +00:00
|
|
|
from pySim.construct import build_construct, parse_construct
|
2021-05-24 21:15:54 +00:00
|
|
|
|
|
|
|
|
2022-02-11 16:08:45 +00:00
|
|
|
def camel_to_snake(name):
|
|
|
|
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
|
|
|
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
class TlvMeta(abc.ABCMeta):
|
|
|
|
"""Metaclass which we use to set some class variables at the time of defining a subclass.
|
|
|
|
This allows us to create subclasses for each TLV/IE type, where the class represents fixed
|
|
|
|
parameters like the tag/type and instances of it represent the actual TLV data."""
|
2024-02-05 00:11:57 +00:00
|
|
|
def __new__(mcs, name, bases, namespace, **kwargs):
|
|
|
|
#print("TlvMeta_new_(mcs=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (mcs, name, bases, namespace, kwargs))
|
|
|
|
x = super().__new__(mcs, name, bases, namespace)
|
2021-05-24 21:15:54 +00:00
|
|
|
# this becomes a _class_ variable, not an instance variable
|
|
|
|
x.tag = namespace.get('tag', kwargs.get('tag', None))
|
|
|
|
x.desc = namespace.get('desc', kwargs.get('desc', None))
|
|
|
|
nested = namespace.get('nested', kwargs.get('nested', None))
|
|
|
|
if nested is None or inspect.isclass(nested) and issubclass(nested, TLV_IE_Collection):
|
|
|
|
# caller has specified TLV_IE_Collection sub-class, we can directly reference it
|
|
|
|
x.nested_collection_cls = nested
|
|
|
|
else:
|
|
|
|
# caller passed list of other TLV classes that might possibly appear within us,
|
|
|
|
# build a dynamically-created TLV_IE_Collection sub-class and reference it
|
|
|
|
name = 'auto_collection_%s' % (name)
|
|
|
|
cls = type(name, (TLV_IE_Collection,), {'nested': nested})
|
|
|
|
x.nested_collection_cls = cls
|
|
|
|
return x
|
|
|
|
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
class TlvCollectionMeta(abc.ABCMeta):
|
|
|
|
"""Metaclass which we use to set some class variables at the time of defining a subclass.
|
|
|
|
This allows us to create subclasses for each Collection type, where the class represents fixed
|
|
|
|
parameters like the nested IE classes and instances of it represent the actual TLV data."""
|
2024-02-05 00:11:57 +00:00
|
|
|
def __new__(mcs, name, bases, namespace, **kwargs):
|
|
|
|
#print("TlvCollectionMeta_new_(mcs=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (mcs, name, bases, namespace, kwargs))
|
|
|
|
x = super().__new__(mcs, name, bases, namespace)
|
2021-05-24 21:15:54 +00:00
|
|
|
# this becomes a _class_ variable, not an instance variable
|
|
|
|
x.possible_nested = namespace.get('nested', kwargs.get('nested', None))
|
|
|
|
return x
|
|
|
|
|
|
|
|
|
|
|
|
class Transcodable(abc.ABC):
|
|
|
|
_construct = None
|
|
|
|
"""Base class for something that can be encoded + encoded. Decoding and Encoding happens either
|
|
|
|
* via a 'construct' object stored in a derived class' _construct variable, or
|
|
|
|
* via a 'construct' object stored in an instance _construct variable, or
|
|
|
|
* via a derived class' _{to,from}_bytes() methods."""
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
def __init__(self):
|
|
|
|
self.encoded = None
|
|
|
|
self.decoded = None
|
|
|
|
self._construct = None
|
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def to_bytes(self, context: dict = {}) -> bytes:
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Convert from internal representation to binary bytes. Store the binary result
|
|
|
|
in the internal state and return it."""
|
2024-02-05 00:11:57 +00:00
|
|
|
if self.decoded is None:
|
2021-10-21 08:02:10 +00:00
|
|
|
do = b''
|
|
|
|
elif self._construct:
|
2023-12-17 09:07:01 +00:00
|
|
|
do = build_construct(self._construct, self.decoded, context)
|
2021-05-24 21:15:54 +00:00
|
|
|
elif self.__class__._construct:
|
2023-12-17 09:07:01 +00:00
|
|
|
do = build_construct(self.__class__._construct, self.decoded, context)
|
2021-05-24 21:15:54 +00:00
|
|
|
else:
|
|
|
|
do = self._to_bytes()
|
|
|
|
self.encoded = do
|
|
|
|
return do
|
|
|
|
|
|
|
|
# not an abstractmethod, as it is only required if no _construct exists
|
|
|
|
def _to_bytes(self):
|
2022-07-20 05:51:13 +00:00
|
|
|
raise NotImplementedError('%s._to_bytes' % type(self).__name__)
|
2021-05-24 21:15:54 +00:00
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def from_bytes(self, do: bytes, context: dict = {}):
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Convert from binary bytes to internal representation. Store the decoded result
|
|
|
|
in the internal state and return it."""
|
|
|
|
self.encoded = do
|
2021-10-21 08:02:10 +00:00
|
|
|
if self.encoded == b'':
|
|
|
|
self.decoded = None
|
|
|
|
elif self._construct:
|
2023-12-17 09:07:01 +00:00
|
|
|
self.decoded = parse_construct(self._construct, do, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
elif self.__class__._construct:
|
2023-12-17 09:07:01 +00:00
|
|
|
self.decoded = parse_construct(self.__class__._construct, do, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
else:
|
|
|
|
self.decoded = self._from_bytes(do)
|
|
|
|
return self.decoded
|
|
|
|
|
|
|
|
# not an abstractmethod, as it is only required if no _construct exists
|
2022-02-10 17:05:45 +00:00
|
|
|
def _from_bytes(self, do: bytes):
|
2022-07-20 05:51:13 +00:00
|
|
|
raise NotImplementedError('%s._from_bytes' % type(self).__name__)
|
2021-05-24 21:15:54 +00:00
|
|
|
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
class IE(Transcodable, metaclass=TlvMeta):
|
|
|
|
# we specify the metaclass so any downstream subclasses will automatically use it
|
|
|
|
"""Base class for various Information Elements. We understand the notion of a hierarchy
|
|
|
|
of IEs on top of the Transcodable class."""
|
|
|
|
# this is overridden by the TlvMeta metaclass, if it is used to create subclasses
|
|
|
|
nested_collection_cls = None
|
|
|
|
tag = None
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
super().__init__()
|
|
|
|
self.nested_collection = None
|
|
|
|
if self.nested_collection_cls:
|
|
|
|
self.nested_collection = self.nested_collection_cls()
|
|
|
|
# if we are a constructed IE, [ordered] list of actual child-IE instances
|
|
|
|
self.children = kwargs.get('children', [])
|
|
|
|
self.decoded = kwargs.get('decoded', None)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
"""Return a string representing the [nested] IE data (for print)."""
|
|
|
|
if len(self.children):
|
|
|
|
member_strs = [repr(x) for x in self.children]
|
|
|
|
return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
|
|
|
|
else:
|
|
|
|
return '%s(%s)' % (type(self).__name__, self.decoded)
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
"""Return a JSON-serializable dict representing the [nested] IE data."""
|
|
|
|
if len(self.children):
|
|
|
|
v = [x.to_dict() for x in self.children]
|
|
|
|
else:
|
|
|
|
v = self.decoded
|
2022-02-11 16:08:45 +00:00
|
|
|
return {camel_to_snake(type(self).__name__): v}
|
2021-05-24 21:15:54 +00:00
|
|
|
|
2022-02-10 17:05:45 +00:00
|
|
|
def from_dict(self, decoded: dict):
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Set the IE internal decoded representation to data from the argument.
|
|
|
|
If this is a nested IE, the child IE instance list is re-created."""
|
2023-12-27 14:52:11 +00:00
|
|
|
expected_key_name = camel_to_snake(type(self).__name__)
|
|
|
|
if not expected_key_name in decoded:
|
|
|
|
raise ValueError("Dict %s doesn't contain expected key %s" % (decoded, expected_key_name))
|
2021-05-24 21:15:54 +00:00
|
|
|
if self.nested_collection:
|
2023-12-27 14:52:11 +00:00
|
|
|
self.children = self.nested_collection.from_dict(decoded[expected_key_name])
|
2021-05-24 21:15:54 +00:00
|
|
|
else:
|
|
|
|
self.children = []
|
tlv: Fix IE.from_dict() method
The existing IE.from_dict() method *supposedly* accepts a dict as
input value, but it actually expects the raw decoded value, unless it is
a nested IE. This is inconsistent in various ways, and results in a bug
visible at a higher layer, such as files like EF.{DOMAIN,IMPI,IMPU},
which are transparent files containing a single BER-TLV IE.
Decoding such files worked, but re-encoding them did not, due to the
fact that we'd pass a dict to the from_dict method, which then gets
assigned to self.decoded and further passed along to any later actual
encoder function like to_bytes or to_tlv. In that instance, the dict
might be handed to a self._construct which has no idea how to process
the dict, as it expects the raw decoded value.
Change-Id: I3dd5204510e5c32ef1c4a999258d87cb3f1df8c8
Closes: OS#6073
Related: OS#6072
2023-06-26 08:52:19 +00:00
|
|
|
self.decoded = decoded[expected_key_name]
|
2021-05-24 21:15:54 +00:00
|
|
|
|
|
|
|
def is_constructed(self):
|
|
|
|
"""Is this IE constructed by further nested IEs?"""
|
2024-02-05 00:11:57 +00:00
|
|
|
return bool(len(self.children) > 0)
|
2021-05-24 21:15:54 +00:00
|
|
|
|
|
|
|
@abc.abstractmethod
|
2023-12-17 09:07:01 +00:00
|
|
|
def to_ie(self, context: dict = {}) -> bytes:
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Convert the internal representation to entire IE including IE header."""
|
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def to_bytes(self, context: dict = {}) -> bytes:
|
2023-11-01 16:28:18 +00:00
|
|
|
"""Convert the internal representation *of the value part* to binary bytes."""
|
2021-05-24 21:15:54 +00:00
|
|
|
if self.is_constructed():
|
|
|
|
# concatenate the encoded IE of all children to form the value part
|
|
|
|
out = b''
|
|
|
|
for c in self.children:
|
2023-12-17 09:07:01 +00:00
|
|
|
out += c.to_ie(context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
return out
|
|
|
|
else:
|
2023-12-17 09:07:01 +00:00
|
|
|
return super().to_bytes(context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def from_bytes(self, do: bytes, context: dict = {}):
|
2023-11-01 16:28:18 +00:00
|
|
|
"""Parse *the value part* from binary bytes to internal representation."""
|
2021-05-24 21:15:54 +00:00
|
|
|
if self.nested_collection:
|
2023-12-17 09:07:01 +00:00
|
|
|
self.children = self.nested_collection.from_bytes(do, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
else:
|
|
|
|
self.children = []
|
2023-12-17 09:07:01 +00:00
|
|
|
return super().from_bytes(do, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TLV_IE(IE):
|
|
|
|
"""Abstract base class for various TLV type Information Elements."""
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
def _compute_tag(self) -> int:
|
|
|
|
"""Compute the tag (sometimes the tag encodes part of the value)."""
|
|
|
|
return self.tag
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@abc.abstractmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Obtain the raw TAG at the start of the bytes provided by the user."""
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@abc.abstractmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Obtain the length encoded at the start of the bytes provided by the user."""
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def _encode_tag(self) -> bytes:
|
|
|
|
"""Encode the tag part. Must be provided by derived (TLV format specific) class."""
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _encode_len(self, val: bytes) -> bytes:
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Encode the length part assuming a certain binary value. Must be provided by
|
|
|
|
derived (TLV format specific) class."""
|
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def to_ie(self, context: dict = {}):
|
|
|
|
return self.to_tlv(context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def to_tlv(self, context: dict = {}):
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Convert the internal representation to binary TLV bytes."""
|
2023-12-17 09:07:01 +00:00
|
|
|
val = self.to_bytes(context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
return self._encode_tag() + self._encode_len(val) + val
|
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def from_tlv(self, do: bytes, context: dict = {}):
|
2022-02-11 15:29:32 +00:00
|
|
|
if len(do) == 0:
|
|
|
|
return {}, b''
|
2021-05-24 21:15:54 +00:00
|
|
|
(rawtag, remainder) = self.__class__._parse_tag_raw(do)
|
|
|
|
if rawtag:
|
2022-08-06 11:16:19 +00:00
|
|
|
if rawtag != self._compute_tag():
|
2021-05-24 21:15:54 +00:00
|
|
|
raise ValueError("%s: Encountered tag %s doesn't match our supported tag %s" %
|
|
|
|
(self, rawtag, self.tag))
|
|
|
|
(length, remainder) = self.__class__._parse_len(remainder)
|
|
|
|
value = remainder[:length]
|
|
|
|
remainder = remainder[length:]
|
|
|
|
else:
|
|
|
|
value = do
|
|
|
|
remainder = b''
|
2023-12-17 09:07:01 +00:00
|
|
|
dec = self.from_bytes(value, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
return dec, remainder
|
|
|
|
|
|
|
|
|
|
|
|
class BER_TLV_IE(TLV_IE):
|
|
|
|
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
@classmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
return bertlv_parse_tag(do)
|
|
|
|
|
|
|
|
@classmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
return bertlv_parse_tag_raw(do)
|
|
|
|
|
|
|
|
@classmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
return bertlv_parse_len(do)
|
|
|
|
|
|
|
|
def _encode_tag(self) -> bytes:
|
|
|
|
return bertlv_encode_tag(self._compute_tag())
|
|
|
|
|
2022-02-10 17:05:45 +00:00
|
|
|
def _encode_len(self, val: bytes) -> bytes:
|
2021-05-24 21:15:54 +00:00
|
|
|
return bertlv_encode_len(len(val))
|
|
|
|
|
|
|
|
|
|
|
|
class COMPR_TLV_IE(TLV_IE):
|
|
|
|
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
def __init__(self, **kwargs):
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
self.comprehension = False
|
|
|
|
|
|
|
|
@classmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
return comprehensiontlv_parse_tag(do)
|
|
|
|
|
|
|
|
@classmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
return comprehensiontlv_parse_tag_raw(do)
|
|
|
|
|
|
|
|
@classmethod
|
2022-02-10 17:05:45 +00:00
|
|
|
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
2021-05-24 21:15:54 +00:00
|
|
|
return bertlv_parse_len(do)
|
|
|
|
|
|
|
|
def _encode_tag(self) -> bytes:
|
|
|
|
return comprehensiontlv_encode_tag(self._compute_tag())
|
|
|
|
|
2022-02-10 17:05:45 +00:00
|
|
|
def _encode_len(self, val: bytes) -> bytes:
|
2021-05-24 21:15:54 +00:00
|
|
|
return bertlv_encode_len(len(val))
|
|
|
|
|
|
|
|
|
2024-01-14 09:09:07 +00:00
|
|
|
class DGI_TLV_IE(TLV_IE):
|
|
|
|
"""TLV_IE formated as GlobalPlatform Systems Scripting Language Specification v1.1.0 Annex B."""
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
|
|
|
return dgi_parse_tag_raw(do)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
|
|
|
return dgi_parse_len(do)
|
|
|
|
|
|
|
|
def _encode_tag(self) -> bytes:
|
|
|
|
return dgi_encode_tag(self._compute_tag())
|
|
|
|
|
|
|
|
def _encode_len(self, val: bytes) -> bytes:
|
|
|
|
return dgi_encode_len(len(val))
|
|
|
|
|
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
class TLV_IE_Collection(metaclass=TlvCollectionMeta):
|
|
|
|
# we specify the metaclass so any downstream subclasses will automatically use it
|
|
|
|
"""A TLV_IE_Collection consists of multiple TLV_IE classes identified by their tags.
|
|
|
|
A given encoded DO may contain any of them in any order, and may contain multiple instances
|
|
|
|
of each DO."""
|
|
|
|
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
|
|
|
|
possible_nested = []
|
2022-02-10 17:05:45 +00:00
|
|
|
|
2021-05-24 21:15:54 +00:00
|
|
|
def __init__(self, desc=None, **kwargs):
|
|
|
|
self.desc = desc
|
|
|
|
#print("possible_nested: ", self.possible_nested)
|
|
|
|
self.members = kwargs.get('nested', self.possible_nested)
|
|
|
|
self.members_by_tag = {}
|
|
|
|
self.members_by_name = {}
|
2022-02-10 17:05:45 +00:00
|
|
|
self.members_by_tag = {m.tag: m for m in self.members}
|
2023-07-09 19:25:14 +00:00
|
|
|
self.members_by_name = {camel_to_snake(m.__name__): m for m in self.members}
|
2021-05-24 21:15:54 +00:00
|
|
|
# if we are a constructed IE, [ordered] list of actual child-IE instances
|
|
|
|
self.children = kwargs.get('children', [])
|
|
|
|
self.encoded = None
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
member_strs = [str(x) for x in self.members]
|
|
|
|
return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
member_strs = [repr(x) for x in self.members]
|
|
|
|
return '%s(%s)' % (self.__class__, ','.join(member_strs))
|
|
|
|
|
|
|
|
def __add__(self, other):
|
|
|
|
"""Extending TLV_IE_Collections with other TLV_IE_Collections or TLV_IEs."""
|
|
|
|
if isinstance(other, TLV_IE_Collection):
|
|
|
|
# adding one collection to another
|
|
|
|
members = self.members + other.members
|
|
|
|
return TLV_IE_Collection(self.desc, nested=members)
|
|
|
|
elif inspect.isclass(other) and issubclass(other, TLV_IE):
|
|
|
|
# adding a member to a collection
|
2022-02-10 17:05:45 +00:00
|
|
|
return TLV_IE_Collection(self.desc, nested=self.members + [other])
|
2021-05-24 21:15:54 +00:00
|
|
|
else:
|
|
|
|
raise TypeError
|
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def from_bytes(self, binary: bytes, context: dict = {}) -> List[TLV_IE]:
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Create a list of TLV_IEs from the collection based on binary input data.
|
|
|
|
Args:
|
|
|
|
binary : binary bytes of encoded data
|
|
|
|
Returns:
|
|
|
|
list of instances of TLV_IE sub-classes containing parsed data
|
|
|
|
"""
|
|
|
|
self.encoded = binary
|
|
|
|
# list of instances of TLV_IE collection member classes appearing in the data
|
|
|
|
res = []
|
|
|
|
remainder = binary
|
|
|
|
first = next(iter(self.members_by_tag.values()))
|
|
|
|
# iterate until no binary trailer is left
|
|
|
|
while len(remainder):
|
2023-12-17 09:07:01 +00:00
|
|
|
context['siblings'] = res
|
2021-05-24 21:15:54 +00:00
|
|
|
# obtain the tag at the start of the remainder
|
2024-02-05 00:11:57 +00:00
|
|
|
tag, _r = first._parse_tag_raw(remainder)
|
|
|
|
if tag is None:
|
2023-12-28 08:21:17 +00:00
|
|
|
break
|
2021-05-24 21:15:54 +00:00
|
|
|
if tag in self.members_by_tag:
|
|
|
|
cls = self.members_by_tag[tag]
|
|
|
|
# create an instance and parse accordingly
|
|
|
|
inst = cls()
|
2024-02-05 00:11:57 +00:00
|
|
|
_dec, remainder = inst.from_tlv(remainder, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
res.append(inst)
|
|
|
|
else:
|
|
|
|
# unknown tag; create the related class on-the-fly using the same base class
|
|
|
|
name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
|
2022-02-10 17:05:45 +00:00
|
|
|
cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
|
|
|
|
'nested_collection_cls': None})
|
|
|
|
cls._from_bytes = lambda s, a: {'raw': a.hex()}
|
2021-05-24 21:15:54 +00:00
|
|
|
cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
|
|
|
|
# create an instance and parse accordingly
|
|
|
|
inst = cls()
|
2024-02-05 00:11:57 +00:00
|
|
|
_dec, remainder = inst.from_tlv(remainder, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
res.append(inst)
|
|
|
|
self.children = res
|
|
|
|
return res
|
|
|
|
|
2022-02-10 17:05:45 +00:00
|
|
|
def from_dict(self, decoded: List[dict]) -> List[TLV_IE]:
|
2021-05-24 21:15:54 +00:00
|
|
|
"""Create a list of TLV_IE instances from the collection based on an array
|
|
|
|
of dicts, where they key indicates the name of the TLV_IE subclass to use."""
|
|
|
|
# list of instances of TLV_IE collection member classes appearing in the data
|
|
|
|
res = []
|
pySim/tlv.py: Fix TLV_IE_Collection from_dict with nested collections
This is all quite complicated. In general, the TLV_IE.to_dict() method
obviously is expected to return a dict (with key equal to the snake-case
name of the class, value to the decode IE value). This single-entry
dict can then be passed back to the from_dict() method to build the
binary representation.
However, with a TLV_IE_Collection, any TLV_IE can occur any number of
times, so we need an array to represent it (dict would need unique key,
which doesn't exist in multiple instances of same TLV IE). Hence, the
TLV_IE_Collection.to_dict() method actually returns a list of dicts,
rather than a dict itself. Each dict in the list represents one TLV_IE.
When encoding such a TLV_IE_Collection back from the list-of-dicts, we
so far didn't handle this special case and tried to de-serialize with
a class-name-keyed dict, which doesn't work.
This patch fixes a regression in the aram_store_ref_ar_do pySim-shell
command which got introduced in Change-Id I3dd5204510e5c32ef1c4a999258d87cb3f1df8c8
While we're fixing it, add some additional comments to why things are
how they are.
Change-Id: Ibdd30cf1652c864f167b1b655b49a87941e15fd5
2023-07-09 19:28:13 +00:00
|
|
|
# iterate over members of the list passed into "decoded"
|
2021-05-24 21:15:54 +00:00
|
|
|
for i in decoded:
|
pySim/tlv.py: Fix TLV_IE_Collection from_dict with nested collections
This is all quite complicated. In general, the TLV_IE.to_dict() method
obviously is expected to return a dict (with key equal to the snake-case
name of the class, value to the decode IE value). This single-entry
dict can then be passed back to the from_dict() method to build the
binary representation.
However, with a TLV_IE_Collection, any TLV_IE can occur any number of
times, so we need an array to represent it (dict would need unique key,
which doesn't exist in multiple instances of same TLV IE). Hence, the
TLV_IE_Collection.to_dict() method actually returns a list of dicts,
rather than a dict itself. Each dict in the list represents one TLV_IE.
When encoding such a TLV_IE_Collection back from the list-of-dicts, we
so far didn't handle this special case and tried to de-serialize with
a class-name-keyed dict, which doesn't work.
This patch fixes a regression in the aram_store_ref_ar_do pySim-shell
command which got introduced in Change-Id I3dd5204510e5c32ef1c4a999258d87cb3f1df8c8
While we're fixing it, add some additional comments to why things are
how they are.
Change-Id: Ibdd30cf1652c864f167b1b655b49a87941e15fd5
2023-07-09 19:28:13 +00:00
|
|
|
# iterate over all the keys (typically one!) within the current list item dict
|
2021-05-24 21:15:54 +00:00
|
|
|
for k in i.keys():
|
pySim/tlv.py: Fix TLV_IE_Collection from_dict with nested collections
This is all quite complicated. In general, the TLV_IE.to_dict() method
obviously is expected to return a dict (with key equal to the snake-case
name of the class, value to the decode IE value). This single-entry
dict can then be passed back to the from_dict() method to build the
binary representation.
However, with a TLV_IE_Collection, any TLV_IE can occur any number of
times, so we need an array to represent it (dict would need unique key,
which doesn't exist in multiple instances of same TLV IE). Hence, the
TLV_IE_Collection.to_dict() method actually returns a list of dicts,
rather than a dict itself. Each dict in the list represents one TLV_IE.
When encoding such a TLV_IE_Collection back from the list-of-dicts, we
so far didn't handle this special case and tried to de-serialize with
a class-name-keyed dict, which doesn't work.
This patch fixes a regression in the aram_store_ref_ar_do pySim-shell
command which got introduced in Change-Id I3dd5204510e5c32ef1c4a999258d87cb3f1df8c8
While we're fixing it, add some additional comments to why things are
how they are.
Change-Id: Ibdd30cf1652c864f167b1b655b49a87941e15fd5
2023-07-09 19:28:13 +00:00
|
|
|
# check if we have a member identified by the dict key
|
2021-05-24 21:15:54 +00:00
|
|
|
if k in self.members_by_name:
|
pySim/tlv.py: Fix TLV_IE_Collection from_dict with nested collections
This is all quite complicated. In general, the TLV_IE.to_dict() method
obviously is expected to return a dict (with key equal to the snake-case
name of the class, value to the decode IE value). This single-entry
dict can then be passed back to the from_dict() method to build the
binary representation.
However, with a TLV_IE_Collection, any TLV_IE can occur any number of
times, so we need an array to represent it (dict would need unique key,
which doesn't exist in multiple instances of same TLV IE). Hence, the
TLV_IE_Collection.to_dict() method actually returns a list of dicts,
rather than a dict itself. Each dict in the list represents one TLV_IE.
When encoding such a TLV_IE_Collection back from the list-of-dicts, we
so far didn't handle this special case and tried to de-serialize with
a class-name-keyed dict, which doesn't work.
This patch fixes a regression in the aram_store_ref_ar_do pySim-shell
command which got introduced in Change-Id I3dd5204510e5c32ef1c4a999258d87cb3f1df8c8
While we're fixing it, add some additional comments to why things are
how they are.
Change-Id: Ibdd30cf1652c864f167b1b655b49a87941e15fd5
2023-07-09 19:28:13 +00:00
|
|
|
# resolve the class for that name; create an instance of it
|
2021-05-24 21:15:54 +00:00
|
|
|
cls = self.members_by_name[k]
|
2021-10-21 09:33:44 +00:00
|
|
|
inst = cls()
|
pySim/tlv.py: Fix TLV_IE_Collection from_dict with nested collections
This is all quite complicated. In general, the TLV_IE.to_dict() method
obviously is expected to return a dict (with key equal to the snake-case
name of the class, value to the decode IE value). This single-entry
dict can then be passed back to the from_dict() method to build the
binary representation.
However, with a TLV_IE_Collection, any TLV_IE can occur any number of
times, so we need an array to represent it (dict would need unique key,
which doesn't exist in multiple instances of same TLV IE). Hence, the
TLV_IE_Collection.to_dict() method actually returns a list of dicts,
rather than a dict itself. Each dict in the list represents one TLV_IE.
When encoding such a TLV_IE_Collection back from the list-of-dicts, we
so far didn't handle this special case and tried to de-serialize with
a class-name-keyed dict, which doesn't work.
This patch fixes a regression in the aram_store_ref_ar_do pySim-shell
command which got introduced in Change-Id I3dd5204510e5c32ef1c4a999258d87cb3f1df8c8
While we're fixing it, add some additional comments to why things are
how they are.
Change-Id: Ibdd30cf1652c864f167b1b655b49a87941e15fd5
2023-07-09 19:28:13 +00:00
|
|
|
if cls.nested_collection_cls:
|
|
|
|
# in case of collections, we want to pass the raw "value" portion to from_dict,
|
|
|
|
# as to_dict() below intentionally omits the collection-class-name as key
|
|
|
|
inst.from_dict(i[k])
|
|
|
|
else:
|
|
|
|
inst.from_dict({k: i[k]})
|
2021-05-24 21:15:54 +00:00
|
|
|
res.append(inst)
|
|
|
|
else:
|
|
|
|
raise ValueError('%s: Unknown TLV Class %s in %s; expected %s' %
|
2023-07-09 19:27:07 +00:00
|
|
|
(self, k, decoded, self.members_by_name.keys()))
|
2021-05-24 21:15:54 +00:00
|
|
|
self.children = res
|
|
|
|
return res
|
|
|
|
|
|
|
|
def to_dict(self):
|
pySim/tlv.py: Fix TLV_IE_Collection from_dict with nested collections
This is all quite complicated. In general, the TLV_IE.to_dict() method
obviously is expected to return a dict (with key equal to the snake-case
name of the class, value to the decode IE value). This single-entry
dict can then be passed back to the from_dict() method to build the
binary representation.
However, with a TLV_IE_Collection, any TLV_IE can occur any number of
times, so we need an array to represent it (dict would need unique key,
which doesn't exist in multiple instances of same TLV IE). Hence, the
TLV_IE_Collection.to_dict() method actually returns a list of dicts,
rather than a dict itself. Each dict in the list represents one TLV_IE.
When encoding such a TLV_IE_Collection back from the list-of-dicts, we
so far didn't handle this special case and tried to de-serialize with
a class-name-keyed dict, which doesn't work.
This patch fixes a regression in the aram_store_ref_ar_do pySim-shell
command which got introduced in Change-Id I3dd5204510e5c32ef1c4a999258d87cb3f1df8c8
While we're fixing it, add some additional comments to why things are
how they are.
Change-Id: Ibdd30cf1652c864f167b1b655b49a87941e15fd5
2023-07-09 19:28:13 +00:00
|
|
|
# we intentionally return not a dict, but a list of dicts. We could prefix by
|
|
|
|
# self.__class__.__name__, but that is usually some meaningless auto-generated collection name.
|
2021-05-24 21:15:54 +00:00
|
|
|
return [x.to_dict() for x in self.children]
|
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def to_bytes(self, context: dict = {}):
|
2021-05-24 21:15:54 +00:00
|
|
|
out = b''
|
2023-12-17 09:07:01 +00:00
|
|
|
context['siblings'] = self.children
|
2021-05-24 21:15:54 +00:00
|
|
|
for c in self.children:
|
2023-12-17 09:07:01 +00:00
|
|
|
out += c.to_tlv(context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
return out
|
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def from_tlv(self, do, context: dict = {}):
|
|
|
|
return self.from_bytes(do, context=context)
|
2021-05-24 21:15:54 +00:00
|
|
|
|
2023-12-17 09:07:01 +00:00
|
|
|
def to_tlv(self, context: dict = {}):
|
|
|
|
return self.to_bytes(context=context)
|
tlv: Function for flattening the list-of-dict output of TLV decoder
Before:
{
"FcpTemplate": [
{
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
}
},
{
"FileIdentifier": "3f00"
},
{
"ProprietaryInformation": [
{
"UiccCharacteristics": "71"
},
{
"AvailableMemory": 123052
}
]
},
{
"LifeCycleStatusInteger": "operational_activated"
},
{
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
}
},
{
"PinStatusTemplate_DO": [
{
"PS_DO": "40"
},
{
"KeyReference": 1
},
{
"KeyReference": 129
}
]
},
{
"TotalFileSize": 187809
}
]
}
After:
{
"FcpTemplate": {
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
},
"FileIdentifier": "3f00",
"ProprietaryInformation": {
"UiccCharacteristics": "71",
"AvailableMemory": 123052
},
"LifeCycleStatusInteger": "operational_activated",
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
},
"PinStatusTemplate_DO": {
"PS_DO": "40",
"KeyReference": 129
},
"TotalFileSize": 187809
}
}
Change-Id: Ia5ad8f1d3b0d47ebdb1856b0feaba120bad3eef9
2022-02-11 14:44:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
def flatten_dict_lists(inp):
|
|
|
|
"""hierarchically flatten each list-of-dicts into a single dict. This is useful to
|
|
|
|
make the output of hierarchical TLV decoder structures flatter and more easy to read."""
|
|
|
|
def are_all_elements_dict(l):
|
|
|
|
for e in l:
|
|
|
|
if not isinstance(e, dict):
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2023-12-06 08:01:00 +00:00
|
|
|
def are_elements_unique(lod):
|
2024-02-05 00:11:57 +00:00
|
|
|
set_of_keys = {list(x.keys())[0] for x in lod}
|
2023-12-06 08:01:00 +00:00
|
|
|
return len(lod) == len(set_of_keys)
|
|
|
|
|
tlv: Function for flattening the list-of-dict output of TLV decoder
Before:
{
"FcpTemplate": [
{
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
}
},
{
"FileIdentifier": "3f00"
},
{
"ProprietaryInformation": [
{
"UiccCharacteristics": "71"
},
{
"AvailableMemory": 123052
}
]
},
{
"LifeCycleStatusInteger": "operational_activated"
},
{
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
}
},
{
"PinStatusTemplate_DO": [
{
"PS_DO": "40"
},
{
"KeyReference": 1
},
{
"KeyReference": 129
}
]
},
{
"TotalFileSize": 187809
}
]
}
After:
{
"FcpTemplate": {
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
},
"FileIdentifier": "3f00",
"ProprietaryInformation": {
"UiccCharacteristics": "71",
"AvailableMemory": 123052
},
"LifeCycleStatusInteger": "operational_activated",
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
},
"PinStatusTemplate_DO": {
"PS_DO": "40",
"KeyReference": 129
},
"TotalFileSize": 187809
}
}
Change-Id: Ia5ad8f1d3b0d47ebdb1856b0feaba120bad3eef9
2022-02-11 14:44:28 +00:00
|
|
|
if isinstance(inp, list):
|
2023-12-06 08:01:00 +00:00
|
|
|
if are_all_elements_dict(inp) and are_elements_unique(inp):
|
tlv: Function for flattening the list-of-dict output of TLV decoder
Before:
{
"FcpTemplate": [
{
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
}
},
{
"FileIdentifier": "3f00"
},
{
"ProprietaryInformation": [
{
"UiccCharacteristics": "71"
},
{
"AvailableMemory": 123052
}
]
},
{
"LifeCycleStatusInteger": "operational_activated"
},
{
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
}
},
{
"PinStatusTemplate_DO": [
{
"PS_DO": "40"
},
{
"KeyReference": 1
},
{
"KeyReference": 129
}
]
},
{
"TotalFileSize": 187809
}
]
}
After:
{
"FcpTemplate": {
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
},
"FileIdentifier": "3f00",
"ProprietaryInformation": {
"UiccCharacteristics": "71",
"AvailableMemory": 123052
},
"LifeCycleStatusInteger": "operational_activated",
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
},
"PinStatusTemplate_DO": {
"PS_DO": "40",
"KeyReference": 129
},
"TotalFileSize": 187809
}
}
Change-Id: Ia5ad8f1d3b0d47ebdb1856b0feaba120bad3eef9
2022-02-11 14:44:28 +00:00
|
|
|
# flatten into one shared dict
|
|
|
|
newdict = {}
|
|
|
|
for e in inp:
|
|
|
|
key = list(e.keys())[0]
|
|
|
|
newdict[key] = e[key]
|
|
|
|
inp = newdict
|
|
|
|
# process result as any native dict
|
2024-02-05 00:11:57 +00:00
|
|
|
return {k:flatten_dict_lists(v) for k,v in inp.items()}
|
tlv: Function for flattening the list-of-dict output of TLV decoder
Before:
{
"FcpTemplate": [
{
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
}
},
{
"FileIdentifier": "3f00"
},
{
"ProprietaryInformation": [
{
"UiccCharacteristics": "71"
},
{
"AvailableMemory": 123052
}
]
},
{
"LifeCycleStatusInteger": "operational_activated"
},
{
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
}
},
{
"PinStatusTemplate_DO": [
{
"PS_DO": "40"
},
{
"KeyReference": 1
},
{
"KeyReference": 129
}
]
},
{
"TotalFileSize": 187809
}
]
}
After:
{
"FcpTemplate": {
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
},
"FileIdentifier": "3f00",
"ProprietaryInformation": {
"UiccCharacteristics": "71",
"AvailableMemory": 123052
},
"LifeCycleStatusInteger": "operational_activated",
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
},
"PinStatusTemplate_DO": {
"PS_DO": "40",
"KeyReference": 129
},
"TotalFileSize": 187809
}
}
Change-Id: Ia5ad8f1d3b0d47ebdb1856b0feaba120bad3eef9
2022-02-11 14:44:28 +00:00
|
|
|
else:
|
|
|
|
return [flatten_dict_lists(x) for x in inp]
|
|
|
|
elif isinstance(inp, dict):
|
2024-02-05 00:11:57 +00:00
|
|
|
return {k:flatten_dict_lists(v) for k,v in inp.items()}
|
tlv: Function for flattening the list-of-dict output of TLV decoder
Before:
{
"FcpTemplate": [
{
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
}
},
{
"FileIdentifier": "3f00"
},
{
"ProprietaryInformation": [
{
"UiccCharacteristics": "71"
},
{
"AvailableMemory": 123052
}
]
},
{
"LifeCycleStatusInteger": "operational_activated"
},
{
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
}
},
{
"PinStatusTemplate_DO": [
{
"PS_DO": "40"
},
{
"KeyReference": 1
},
{
"KeyReference": 129
}
]
},
{
"TotalFileSize": 187809
}
]
}
After:
{
"FcpTemplate": {
"FileDescriptor": {
"shareable": true,
"file_type": "df",
"structure": "no_info_given"
},
"FileIdentifier": "3f00",
"ProprietaryInformation": {
"UiccCharacteristics": "71",
"AvailableMemory": 123052
},
"LifeCycleStatusInteger": "operational_activated",
"SecurityAttribReferenced": {
"ef_arr_file_id": "2f06",
"ef_arr_record_nr": 2
},
"PinStatusTemplate_DO": {
"PS_DO": "40",
"KeyReference": 129
},
"TotalFileSize": 187809
}
}
Change-Id: Ia5ad8f1d3b0d47ebdb1856b0feaba120bad3eef9
2022-02-11 14:44:28 +00:00
|
|
|
else:
|
|
|
|
return inp
|