<p>laforge <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/pysim/+/26161">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  laforge: Looks good to me, approved

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">filesystem: fix decode_select_response<br><br>There are some problems with the usage of decode_select_response. At the<br>moment the ADF files overload the related method to provide decoding of<br>the select responses as per 3gpp TS 102 221. However, this also means<br>that the decoder is only available under ADF.USIM and ADF.ISIM. DF.GSM<br>and DF.TELECOM also overload the decoder method, just like an ADF would<br>do. This decoding method is then implemented as per 3gpp TS 51 011.<br>Since this a a problem on UICCs, the method detects the magic byte 0x62<br>that can be found at the beginning on every select response of an UICC<br>to defer to the TS 102 221 decoding method. TS 51 011 defines the first<br>two bytes of the select response as RFU. This at least problematic.<br><br>To solve this there should be a default method for<br>decode_select_response in the profile, which can be used if no file<br>overloads it with a specific decoder. ADFs use specific decoders, but<br>everything else should use the default decoder. When we deal with an<br>UICC, we expect the select response to be consistantly conform to TS<br>102 221, if we deal with a clasic sim we expect responses as per TS 51<br>011 only.<br><br>Since it is still possible to replace the select response decoder we<br>still have the opportunity to have custom select response in cartain<br>DFs and ADFs should we need them.<br><br>Change-Id: I95e33ec1755727dc9bbbc6016ce2d99a9e66f214<br>Related: OS#5274<br>---<br>M pySim/filesystem.py<br>M pySim/gsm_r.py<br>M pySim/ts_102_221.py<br>M pySim/ts_51_011.py<br>4 files changed, 60 insertions(+), 18 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/pySim/filesystem.py b/pySim/filesystem.py</span><br><span>index dcc2608..3caa470 100644</span><br><span>--- a/pySim/filesystem.py</span><br><span>+++ b/pySim/filesystem.py</span><br><span>@@ -52,7 +52,7 @@</span><br><span>     RESERVED_FIDS = ['3f00']</span><br><span> </span><br><span>     def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None,</span><br><span style="color: hsl(0, 100%, 40%);">-                 parent:Optional['CardDF']=None):</span><br><span style="color: hsl(120, 100%, 40%);">+                 parent:Optional['CardDF']=None, profile:Optional['CardProfile']=None):</span><br><span>         """</span><br><span>         Args:</span><br><span>             fid : File Identifier (4 hex digits)</span><br><span>@@ -60,6 +60,7 @@</span><br><span>             name : Brief name of the file, lik EF_ICCID</span><br><span>             desc : Description of the file</span><br><span>             parent : Parent CardFile object within filesystem hierarchy</span><br><span style="color: hsl(120, 100%, 40%);">+            profile : Card profile that this file should be part of</span><br><span>         """</span><br><span>         if not isinstance(self, CardADF) and fid == None:</span><br><span>             raise ValueError("fid is mandatory")</span><br><span>@@ -72,6 +73,7 @@</span><br><span>         self.parent = parent</span><br><span>         if self.parent and self.parent != self and self.fid:</span><br><span>             self.parent.add_file(self)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.profile = profile</span><br><span>         self.shell_commands = [] # type: List[CommandSet]</span><br><span> </span><br><span>  # Note: the basic properties (fid, name, ect.) are verified when</span><br><span>@@ -173,10 +175,34 @@</span><br><span>         return list(sels.keys())</span><br><span> </span><br><span>     def decode_select_response(self, data_hex:str):</span><br><span style="color: hsl(0, 100%, 40%);">-        """Decode the response to a SELECT command."""</span><br><span style="color: hsl(120, 100%, 40%);">+        """Decode the response to a SELECT command.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        Args:</span><br><span style="color: hsl(120, 100%, 40%);">+          data_hex: Hex string of the select response</span><br><span style="color: hsl(120, 100%, 40%);">+       """</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  # When the current file does not implement a custom select response decoder,</span><br><span style="color: hsl(120, 100%, 40%);">+  # we just ask the parent file to decode the select response. If this method</span><br><span style="color: hsl(120, 100%, 40%);">+   # is not overloaded by the current file we will again ask the parent file.</span><br><span style="color: hsl(120, 100%, 40%);">+    # This way we recursively travel up the file system tree until we hit a file</span><br><span style="color: hsl(120, 100%, 40%);">+  # that does implement a concrete decoder.</span><br><span>         if self.parent:</span><br><span>             return self.parent.decode_select_response(data_hex)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    def get_profile(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Get the profile associated with this file. If this file does not have any</span><br><span style="color: hsl(120, 100%, 40%);">+        profile assigned, try to find a file above (usually the MF) in the filesystem</span><br><span style="color: hsl(120, 100%, 40%);">+        hirarchy that has a profile assigned</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # If we have a profile set, return it</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.profile:</span><br><span style="color: hsl(120, 100%, 40%);">+            return self.profile</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # Walk up recursively until we hit a parent that has a profile set</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.parent:</span><br><span style="color: hsl(120, 100%, 40%);">+            return self.parent.get_profile()</span><br><span style="color: hsl(120, 100%, 40%);">+        return None</span><br><span> </span><br><span> class CardDF(CardFile):</span><br><span>     """DF (Dedicated File) in the smart card filesystem.  Those are basically sub-directories."""</span><br><span>@@ -331,12 +357,18 @@</span><br><span>     def decode_select_response(self, data_hex:str) -> Any:</span><br><span>         """Decode the response to a SELECT command.</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        This is the fall-back method which doesn't perform any decoding. It mostly</span><br><span style="color: hsl(0, 100%, 40%);">-        exists so specific derived classes can overload it for actual decoding.</span><br><span style="color: hsl(120, 100%, 40%);">+        This is the fall-back method which automatically defers to the standard decoding</span><br><span style="color: hsl(120, 100%, 40%);">+        method defined by the card profile. When no profile is set, then no decoding is</span><br><span style="color: hsl(120, 100%, 40%);">+      performed. Specific derived classes (usually ADF) can overload this method to</span><br><span style="color: hsl(120, 100%, 40%);">+ install specific decoding.</span><br><span>         """</span><br><span style="color: hsl(0, 100%, 40%);">-        return data_hex</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+        profile = self.get_profile()</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+        if profile:</span><br><span style="color: hsl(120, 100%, 40%);">+            return profile.decode_select_response(data_hex)</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            return data_hex</span><br><span> </span><br><span> class CardADF(CardDF):</span><br><span>     """ADF (Application Dedicated File) in the smart card filesystem"""</span><br><span>@@ -1029,7 +1061,7 @@</span><br><span>             card : pysim.cards.Card instance</span><br><span>             profile : CardProfile instance</span><br><span>         """</span><br><span style="color: hsl(0, 100%, 40%);">-        self.mf = CardMF()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mf = CardMF(profile=profile)</span><br><span>         self.card = card</span><br><span>         self.selected_file = self.mf # type: CardDF</span><br><span>         self.profile = profile</span><br><span>@@ -1437,6 +1469,19 @@</span><br><span>         """</span><br><span>         return interpret_sw(self.sw, sw)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    def decode_select_response(self, data_hex:str) -> Any:</span><br><span style="color: hsl(120, 100%, 40%);">+        """Decode the response to a SELECT command.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        This is the fall-back method which doesn't perform any decoding. It mostly</span><br><span style="color: hsl(120, 100%, 40%);">+        exists so specific derived classes can overload it for actual decoding.</span><br><span style="color: hsl(120, 100%, 40%);">+        This method is implemented in the profile and is only used when application</span><br><span style="color: hsl(120, 100%, 40%);">+        specific decoding cannot be performed (no ADF is selected).</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        Args:</span><br><span style="color: hsl(120, 100%, 40%);">+          data_hex: Hex string of the select response</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        return data_hex</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> </span><br><span> class CardModel(abc.ABC):</span><br><span>     """A specific card model, typically having some additional vendor-specific files. All</span><br><span>diff --git a/pySim/gsm_r.py b/pySim/gsm_r.py</span><br><span>index 7cd7529..22b88fe 100644</span><br><span>--- a/pySim/gsm_r.py</span><br><span>+++ b/pySim/gsm_r.py</span><br><span>@@ -253,6 +253,3 @@</span><br><span>             EF_DialledVals(fid='6f87', name='EF.FreeNumber', desc='Free Number Call Type 0 and 8'),</span><br><span>           ]</span><br><span>         self.add_files(files)</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-    def decode_select_response(self, data_hex):</span><br><span style="color: hsl(0, 100%, 40%);">-        return pySim.ts_51_011.decode_select_response(data_hex)</span><br><span>diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py</span><br><span>index 3665939..3c99c4d 100644</span><br><span>--- a/pySim/ts_102_221.py</span><br><span>+++ b/pySim/ts_102_221.py</span><br><span>@@ -684,3 +684,6 @@</span><br><span>           }</span><br><span> </span><br><span>         super().__init__('UICC', desc='ETSI TS 102 221', files_in_mf=files, sw=sw)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def decode_select_response(self, data_hex:str) -> Any:</span><br><span style="color: hsl(120, 100%, 40%);">+        return decode_select_response(data_hex)</span><br><span>diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py</span><br><span>index 743c14b..a00cf0d 100644</span><br><span>--- a/pySim/ts_51_011.py</span><br><span>+++ b/pySim/ts_51_011.py</span><br><span>@@ -332,7 +332,6 @@</span><br><span> import enum</span><br><span> </span><br><span> from pySim.filesystem import *</span><br><span style="color: hsl(0, 100%, 40%);">-import pySim.ts_102_221</span><br><span> </span><br><span> ######################################################################</span><br><span> # DF.TELECOM</span><br><span>@@ -451,9 +450,6 @@</span><br><span>           ]</span><br><span>         self.add_files(files)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def decode_select_response(self, data_hex):</span><br><span style="color: hsl(0, 100%, 40%);">-        return decode_select_response(data_hex)</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span> ######################################################################</span><br><span> # DF.GSM</span><br><span> ######################################################################</span><br><span>@@ -936,13 +932,11 @@</span><br><span>           ]</span><br><span>         self.add_files(files)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def decode_select_response(self, data_hex):</span><br><span style="color: hsl(0, 100%, 40%);">-        return decode_select_response(data_hex)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-def decode_select_response(resp_hex):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def _decode_select_response(resp_hex):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>     resp_bin = h2b(resp_hex)</span><br><span style="color: hsl(0, 100%, 40%);">-    if resp_bin[0] == 0x62:</span><br><span style="color: hsl(0, 100%, 40%);">-        return pySim.ts_102_221.decode_select_response(resp_hex)</span><br><span>     struct_of_file_map = {</span><br><span>         0: 'transparent',</span><br><span>         1: 'linear_fixed',</span><br><span>@@ -983,3 +977,6 @@</span><br><span> class CardProfileSIM(CardProfile):</span><br><span>     def __init__(self):</span><br><span>         super().__init__('SIM', desc='GSM SIM Card', files_in_mf=[DF_TELECOM(), DF_GSM()])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def decode_select_response(self, data_hex:str) -> Any:</span><br><span style="color: hsl(120, 100%, 40%);">+       return _decode_select_response(data_hex)</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/pysim/+/26161">change 26161</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/pysim/+/26161"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: pysim </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I95e33ec1755727dc9bbbc6016ce2d99a9e66f214 </div>
<div style="display:none"> Gerrit-Change-Number: 26161 </div>
<div style="display:none"> Gerrit-PatchSet: 6 </div>
<div style="display:none"> Gerrit-Owner: dexter <pmaier@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: daniel <dwillmann@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: laforge <laforge@osmocom.org> </div>
<div style="display:none"> Gerrit-Reviewer: osmith <osmith@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>