Change in pysim[master]: utils: Introduce DataObject representation

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

laforge gerrit-no-reply at lists.osmocom.org
Sun May 2 19:50:05 UTC 2021


laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/24035 )


Change subject: utils: Introduce DataObject representation
......................................................................

utils: Introduce DataObject representation

Represents DataObject (DO) in the sense of ISO 7816-4.  Contrary to
'normal' TLVs where one simply has any number of different TLVs that may
occur in any order at any point, ISO 7816 has the habit of specifying
TLV data but with very specific ordering, or specific choices of tags at
specific points in a stream.  This is represented by DataObjectChoice,
DataObjectCollection and DataObjectSequence classes.

Change-Id: Iac18e7665481c9323cc7d22a3cd93e3da7869deb
---
M pySim/utils.py
1 file changed, 297 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/35/24035/1

diff --git a/pySim/utils.py b/pySim/utils.py
index a177c56..de41dfb 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -863,3 +863,300 @@
 	res += fstr % (heading)
 	res += "#" * width
 	return res
+
+
+
+class DataObject:
+    """A DataObject (DO) in the sense of ISO 7816-4.  Contrary to 'normal' TLVs where one
+    simply has any number of different TLVs that may occur in any order at any point, ISO 7816
+    has the habit of specifying TLV data but with very spcific ordering, or specific choices of
+    tags at specific points in a stream.  This class tries to represent this"""
+    def __init__(self, name, desc = None, tag = None):
+        """
+        Args:
+            name: A brief, all-lowercase, underscore separated string identifier
+            desc: A human-readable description of what this DO represents
+            tag : The tag associated with this DO
+        """
+        self.name = name
+        self.desc = desc
+        self.tag = tag
+        self.encoded = None
+        self.encoded = None
+
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return '%s(%s)' % (self.__class__, self.name)
+
+    def __or__(self, other):
+        """OR-ing DataObjects together renders a DataObjectChoice."""
+        if isinstance(other, DataObject):
+            # DataObject | DataObject = DataObjectChoice
+            return DataObjectChoice(None, members=[self, other])
+        else:
+            raise TypeError
+
+    def __add__(self, other):
+        """ADD-ing DataObjects together renders a DataObjectCollection."""
+        if isinstance(other, DataObject):
+            # DataObject + DataObject = DataObjectCollectin
+            return DataObjectCollection(None, members=[self, other])
+
+    def _compute_tag(self):
+        """Compute the tag (sometimes the tag encodes part of the value)."""
+        return self.tag
+
+    def to_dict(self):
+        """Return a dict in form "name: decoded_value" """
+        return {self.name: self.decoded}
+
+    def from_value(self, do:bytes):
+        """Parse the value part of the DO into the internal state of this instance."""
+        raise TypeError("The derived classes must defined a from_value() method")
+
+    def to_value(self):
+        """Encode the internal state of this instance into the TLV value part."""
+        raise TypeError("The derived classes must defined a to_value() method")
+
+    def from_tlv(self, do:bytes):
+        """Parse binary TLV representation into internal state.  The resulting decoded
+        representation is _not_ returned, but just internalized in the object instance!
+        Args:
+            do : input bytes containing TLV-encoded representation
+        Returns:
+            bytes remaining at end of 'do' after parsing one TLV/DO.
+        """
+        if do[0] != self.tag:
+            raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag))
+        length = do[1]
+        val = do[2:2+length]
+        self.from_value(val)
+        # return remaining bytes
+        return do[2+length:]
+
+    def to_tlv(self):
+        """Encode internal representation to binary TLV.
+        Returns:
+            bytes encoded in TLV format.
+        """
+        val = self.to_value()
+        return bytes(self._compute_tag()) + bytes(len(val)) + val
+
+    # 'codec' interface
+    def decode(self, binary:bytes):
+        """Decode a single DOs from the input data.
+        Args:
+            binary : binary bytes of encoded data
+        Returns:
+            tuple of (decoded_result, binary_remainder)
+        """
+        tag = binary[0]
+        if tag != self.tag:
+            raise ValueError('%s: Unknown Tag 0x%02x in %s; expected 0x%02x' %
+                             (self, tag, binary, self.tag))
+        remainder = self.from_tlv(binary)
+        return (self.to_dict(), remainder)
+
+    # 'codec' interface
+    def encode(self):
+        return self.to_tlv()
+
+class TL0_DataObject(DataObject):
+    """Data Object that has Tag, Len=0 and no Value part."""
+    def __init__(self, name, desc, tag, val=None):
+        super().__init__(name, desc, tag)
+        self.val = val
+
+    def from_value(self, binary:bytes):
+        if len(binary) != 0:
+            raise ValueError
+        self.decoded = self.val
+
+    def to_value(self):
+        return b''
+
+
+class DataObjectCollection:
+    """A DataObjectCollection consits of multiple Data Objects identified by their tags.
+    A given encoded DO may contain any of them in any order, and may contain multiple instances
+    of each DO."""
+    def __init__(self, name, desc = None, members=[]):
+        self.name = name
+        self.desc = desc
+        self.members = members
+        self.members_by_tag = {}
+        self.members_by_name = {}
+        for m in self.members:
+            self.members_by_tag[m.tag] = m
+            self.members_by_name[m.name] = m
+
+    def __str__(self):
+        member_strs = [str(x) for x in self.members]
+        return '%s(%s)' % (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 DataCollections with other DataCollections or DataObjects."""
+        if isinstance(other, DataObjectCollection):
+            # adding one collection to another
+            members = self.members + other.members
+            return DataObjectCollection(self.name, self.desc, members)
+        elif isinstance(other, DataObject):
+            # adding a member to a collection
+            return DataObjectCollection(self.name, self.desc, self.members + [other])
+        else:
+            raise TypeError
+
+    # 'codec' interface
+    def decode(self, binary:bytes):
+        """Decode any number of DOs from the collection until the end of the input data,
+        or uninitialized memory (0xFF) is found.
+        Args:
+            binary : binary bytes of encoded data
+        Returns:
+            tuple of (decoded_result, binary_remainder)
+        """
+        res = []
+        remainder = binary
+        # iterate until no binary trailer is left
+        while len(remainder):
+            tag = remainder[0]
+            if tag == 0xff: # uninitialized memory at the end?
+                return (res, remainder)
+            if not tag in self.members_by_tag:
+                raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
+                                 (self, tag, remainder, self.members_by_tag.keys()))
+            obj = self.members_by_tag[tag]
+            # DO from_tlv returns remainder of binary
+            remainder = obj.from_tlv(remainder)
+            # collect our results
+            res.append(obj.to_dict())
+        return (res, remainder)
+
+    # 'codec' interface
+    def encode(self, decoded):
+        res = bytearray()
+        for i in decoded:
+            obj = self.members_by_name(i[0])
+            res.append(obj.to_tlv())
+        return res
+
+class DataObjectChoice(DataObjectCollection):
+    """One Data Object from within a choice, identified by its tag.
+    This means that exactly one member of the choice must occur, and which one occurs depends
+    on the tag."""
+    def __add__(self, other):
+        """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
+        raise TypeError
+
+    def __or__(self, other):
+        """OR-ing a Choice to another choice extends the choice, as does OR-ing a DataObject."""
+        if isinstance(other, DataObjectChoice):
+            # adding one collection to another
+            members = self.members + other.members
+            return DataObjectChoice(self.name, self.desc, members)
+        elif isinstance(other, DataObject):
+            # adding a member to a collection
+            return DataObjectChoice(self.name, self.desc, self.members + [other])
+        else:
+            raise TypeError
+
+    # 'codec' interface
+    def decode(self, binary:bytes):
+        """Decode a single DOs from the choice based on the tag.
+        Args:
+            binary : binary bytes of encoded data
+        Returns:
+            tuple of (decoded_result, binary_remainder)
+        """
+        tag = binary[0]
+        if tag == 0xff:
+            return (None, binary)
+        if not tag in self.members_by_tag:
+            raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' %
+                             (self, tag, binary, self.members_by_tag.keys()))
+        obj = self.members_by_tag[tag]
+        remainder = obj.from_tlv(binary)
+        return (obj.to_dict(), remainder)
+
+    # 'codec' interface
+    def encode(self, decoded):
+        obj = self.members_by_name(decoded[0])
+        return obj.to_tlv()
+
+class DataObjectSequence:
+    """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
+       ordered sequence of DOs or choices of DOs that have to appear as per the specification.
+       By wrapping them into this formal DataObjectSequence, we can offer convenience methods
+       for encoding or decoding an entire sequence."""
+    def __init__(self, name, desc=None, sequence=[]):
+        self.sequence = sequence
+
+    def __str__(self):
+        member_strs = [str(x) for x in self.sequence]
+        return '%s(%s)' % (self.name, ','.join(member_strs))
+
+    def __repr__(self):
+        member_strs = [repr(x) for x in self.sequence]
+        return '%s(%s)' % (self.__class__, ','.join(member_strs))
+
+    def __add__(self, other):
+        """Add (append) a DataObject or DataObjectChoice to the sequence."""
+        if isinstance(other, 'DataObject'):
+                return DataObjectSequence(self.name, self.desc, self.sequence + [other])
+        elif isinstance(other, 'DataObjectChoice'):
+                return DataObjectSequence(self.name, self.desc, self.sequence + [other])
+        elif isinstance(other, 'DataObjectSequence'):
+                return DataObjectSeuqence(self.name, self.desc, self.sequence + other.sequence)
+
+    # 'codec' interface
+    def decode(self, binary:bytes):
+        """Decode a sequence by calling the decoder of each element in the sequence.
+        Args:
+            binary : binary bytes of encoded data
+        Returns:
+            tuple of (decoded_result, binary_remainder)
+        """
+        remainder = binary
+        res = []
+        for e in self.sequence:
+            (r, remainder) = e.decode(remainder)
+            if r:
+                res.append(r)
+        return (res, remainder)
+
+    # 'codec' interface
+    def decode_multi(self, do:bytes):
+        """Decode multiple occurrences of the sequence from the binary input data.
+        Args:
+            do : binary input data to be decoded
+        Returns:
+            list of results of the decoder of this sequences
+        """
+        remainder = do
+        res = []
+        while len(remainder):
+            (r, remainder2) = self.decode(remainder)
+            if r:
+                res.append(r)
+            if len(remainder2) < len(remainder):
+                remainder = remainder2
+            else:
+                remainder = remainder2
+                break
+        return (res, remainder)
+
+    # 'codec' interface
+    def encode(self, decoded):
+        """Encode a sequence by calling the encoder of each element in the sequence."""
+        encoded = bytearray()
+        i = 0
+        for e in self.sequence:
+            encoded += e.encode(decoded[i])
+            i += 1
+        return encoded

-- 
To view, visit https://gerrit.osmocom.org/c/pysim/+/24035
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Iac18e7665481c9323cc7d22a3cd93e3da7869deb
Gerrit-Change-Number: 24035
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge at osmocom.org>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20210502/567e257e/attachment.htm>


More information about the gerrit-log mailing list