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

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">pySim-shell: allow card insertion at runtime<br><br>Currently a card must be present in the reader until the user can enter<br>pySim-shell. Removing and plugging another card is in theory already<br>possible, but then the new card will operate on the old card and runtime<br>state object. It might also be useful to enter pySim-shell before the<br>card is plugged to execute some other commands for preperation before.<br><br>So lets allow to "equip" pySim-shell with a card and rs object at<br>runtime.<br><br>Related: SYS#5617<br>Change-Id: I9cf532d9da8203065463c7201e7064de6c7ab1b5<br>---<br>M pySim-shell.py<br>M pySim/filesystem.py<br>2 files changed, 80 insertions(+), 19 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/pySim-shell.py b/pySim-shell.py</span><br><span>index 5aa311c..a57aa66 100755</span><br><span>--- a/pySim-shell.py</span><br><span>+++ b/pySim-shell.py</span><br><span>@@ -20,6 +20,7 @@</span><br><span> from typing import List</span><br><span> </span><br><span> import json</span><br><span style="color: hsl(120, 100%, 40%);">+import traceback</span><br><span> </span><br><span> import cmd2</span><br><span> from cmd2 import style, fg, bg</span><br><span>@@ -99,15 +100,12 @@</span><br><span> class PysimApp(cmd2.Cmd):</span><br><span>      CUSTOM_CATEGORY = 'pySim Commands'</span><br><span>   def __init__(self, card, rs, script = None):</span><br><span style="color: hsl(0, 100%, 40%);">-            basic_commands = [Iso7816Commands(), PySimCommands()]</span><br><span>                super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,</span><br><span style="color: hsl(0, 100%, 40%);">-                                 use_ipython=True, auto_load_commands=False, command_sets=basic_commands, startup_script=script)</span><br><span style="color: hsl(120, 100%, 40%);">+                               use_ipython=True, auto_load_commands=False, startup_script=script)</span><br><span>          self.intro = style('Welcome to pySim-shell!', fg=fg.red)</span><br><span>             self.default_category = 'pySim-shell built-in commands'</span><br><span style="color: hsl(0, 100%, 40%);">-         self.card = card</span><br><span style="color: hsl(0, 100%, 40%);">-                iccid, sw = self.card.read_iccid()</span><br><span style="color: hsl(0, 100%, 40%);">-              self.iccid = iccid</span><br><span style="color: hsl(0, 100%, 40%);">-              self.rs = rs</span><br><span style="color: hsl(120, 100%, 40%);">+          self.card = None</span><br><span style="color: hsl(120, 100%, 40%);">+              self.rs = None</span><br><span>               self.py_locals = { 'card': self.card, 'rs' : self.rs }</span><br><span>               self.numeric_path = False</span><br><span>            self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',</span><br><span>@@ -115,13 +113,47 @@</span><br><span>             self.conserve_write = True</span><br><span>           self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',</span><br><span>                                               onchange_cb=self._onchange_conserve_write))</span><br><span style="color: hsl(0, 100%, 40%);">-           self.update_prompt()</span><br><span>                 self.json_pretty_print = True</span><br><span>                self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output'))</span><br><span>              self.apdu_trace = False</span><br><span>              self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',</span><br><span>                                             onchange_cb=self._onchange_apdu_trace))</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+         self.equip(card, rs)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        def equip(self, card, rs):</span><br><span style="color: hsl(120, 100%, 40%);">+            """</span><br><span style="color: hsl(120, 100%, 40%);">+            Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and</span><br><span style="color: hsl(120, 100%, 40%);">+                and commands to enable card operations.</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%);">+          # Unequip everything from pySim-shell that would not work in unequipped state</span><br><span style="color: hsl(120, 100%, 40%);">+         if self.rs:</span><br><span style="color: hsl(120, 100%, 40%);">+                   self.rs.unregister_cmds(self)</span><br><span style="color: hsl(120, 100%, 40%);">+         cmd_set = self.find_commandsets(Iso7816Commands)</span><br><span style="color: hsl(120, 100%, 40%);">+              if cmd_set:</span><br><span style="color: hsl(120, 100%, 40%);">+                   self.unregister_command_set(cmd_set[0])</span><br><span style="color: hsl(120, 100%, 40%);">+               cmd_set = self.find_commandsets(PySimCommands)</span><br><span style="color: hsl(120, 100%, 40%);">+                if cmd_set:</span><br><span style="color: hsl(120, 100%, 40%);">+                   self.unregister_command_set(cmd_set[0])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             self.card = card</span><br><span style="color: hsl(120, 100%, 40%);">+              self.rs = rs</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is</span><br><span style="color: hsl(120, 100%, 40%);">+            # needed to operate on cards.</span><br><span style="color: hsl(120, 100%, 40%);">+         if self.card and self.rs:</span><br><span style="color: hsl(120, 100%, 40%);">+                     self._onchange_conserve_write('conserve_write', False, self.conserve_write)</span><br><span style="color: hsl(120, 100%, 40%);">+                   self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)</span><br><span style="color: hsl(120, 100%, 40%);">+                       self.register_command_set(Iso7816Commands())</span><br><span style="color: hsl(120, 100%, 40%);">+                  self.register_command_set(PySimCommands())</span><br><span style="color: hsl(120, 100%, 40%);">+                    self.iccid, sw = self.card.read_iccid()</span><br><span style="color: hsl(120, 100%, 40%);">+                       rs.select('MF', self)</span><br><span style="color: hsl(120, 100%, 40%);">+         else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 self.poutput("pySim-shell not equipped!")</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+         self.update_prompt()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>       def poutput_json(self, data, force_no_pretty = False):</span><br><span>               """like cmd2.poutput() but for a JSON serializable dict."""</span><br><span>            if force_no_pretty or self.json_pretty_print == False:</span><br><span>@@ -134,13 +166,15 @@</span><br><span>               self.update_prompt()</span><br><span> </span><br><span>     def _onchange_conserve_write(self, param_name, old, new):</span><br><span style="color: hsl(0, 100%, 40%);">-               self.rs.conserve_write = new</span><br><span style="color: hsl(120, 100%, 40%);">+          if self.rs:</span><br><span style="color: hsl(120, 100%, 40%);">+                   self.rs.conserve_write = new</span><br><span> </span><br><span>     def _onchange_apdu_trace(self, param_name, old, new):</span><br><span style="color: hsl(0, 100%, 40%);">-           if new == True:</span><br><span style="color: hsl(0, 100%, 40%);">-                 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)</span><br><span style="color: hsl(0, 100%, 40%);">-              else:</span><br><span style="color: hsl(0, 100%, 40%);">-                   self.card._scc._tp.apdu_tracer = None</span><br><span style="color: hsl(120, 100%, 40%);">+         if self.card:</span><br><span style="color: hsl(120, 100%, 40%);">+                 if new == True:</span><br><span style="color: hsl(120, 100%, 40%);">+                               self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)</span><br><span style="color: hsl(120, 100%, 40%);">+                    else:</span><br><span style="color: hsl(120, 100%, 40%);">+                         self.card._scc._tp.apdu_tracer = None</span><br><span> </span><br><span>    class Cmd2ApduTracer(ApduTracer):</span><br><span>            def __init__(self, cmd2_app):</span><br><span>@@ -151,14 +185,23 @@</span><br><span>                        self.cmd2.poutput("<- %s: %s" % (sw, resp))</span><br><span> </span><br><span>         def update_prompt(self):</span><br><span style="color: hsl(0, 100%, 40%);">-                path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path)</span><br><span style="color: hsl(0, 100%, 40%);">-           self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))</span><br><span style="color: hsl(120, 100%, 40%);">+         if self.rs:</span><br><span style="color: hsl(120, 100%, 40%);">+                   path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path)</span><br><span style="color: hsl(120, 100%, 40%);">+                 self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))</span><br><span style="color: hsl(120, 100%, 40%);">+         else:</span><br><span style="color: hsl(120, 100%, 40%);">+                 self.prompt = 'pySIM-shell (no card)> '</span><br><span> </span><br><span>       @cmd2.with_category(CUSTOM_CATEGORY)</span><br><span>         def do_intro(self, _):</span><br><span>               """Display the intro banner"""</span><br><span>                 self.poutput(self.intro)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+  @cmd2.with_category(CUSTOM_CATEGORY)</span><br><span style="color: hsl(120, 100%, 40%);">+  def do_equip(self, opts):</span><br><span style="color: hsl(120, 100%, 40%);">+             """Equip pySim-shell with card"""</span><br><span style="color: hsl(120, 100%, 40%);">+               rs, card = init_card(sl);</span><br><span style="color: hsl(120, 100%, 40%);">+             self.equip(card, rs)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> </span><br><span> @with_default_category('pySim Commands')</span><br><span> class PySimCommands(CommandSet):</span><br><span>@@ -538,16 +581,29 @@</span><br><span>   # Create command layer</span><br><span>       scc = SimCardCommands(transport=sl)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- rs, card = init_card(sl)</span><br><span style="color: hsl(0, 100%, 40%);">-        if (rs is None or card is None):</span><br><span style="color: hsl(0, 100%, 40%);">-                exit(1)</span><br><span style="color: hsl(0, 100%, 40%);">- app = PysimApp(card, rs, opts.script)</span><br><span style="color: hsl(0, 100%, 40%);">-   rs.select('MF', app)</span><br><span style="color: hsl(120, 100%, 40%);">+  # Detect and initialize the card in the reader. This may fail when there</span><br><span style="color: hsl(120, 100%, 40%);">+      # is no card in the reader or the card is unresponsive. PysimApp is</span><br><span style="color: hsl(120, 100%, 40%);">+   # able to tolerate and recover from that.</span><br><span style="color: hsl(120, 100%, 40%);">+     try:</span><br><span style="color: hsl(120, 100%, 40%);">+          rs, card = init_card(sl)</span><br><span style="color: hsl(120, 100%, 40%);">+              app = PysimApp(card, rs, opts.script)</span><br><span style="color: hsl(120, 100%, 40%);">+ except:</span><br><span style="color: hsl(120, 100%, 40%);">+               print("Card initialization failed with an exception:")</span><br><span style="color: hsl(120, 100%, 40%);">+              print("---------------------8<---------------------")</span><br><span style="color: hsl(120, 100%, 40%);">+            traceback.print_exc()</span><br><span style="color: hsl(120, 100%, 40%);">+         print("---------------------8<---------------------")</span><br><span style="color: hsl(120, 100%, 40%);">+            print("(you may still try to recover from this manually by using the 'equip' command.)")</span><br><span style="color: hsl(120, 100%, 40%);">+            print(" it should also be noted that some readers may behave strangely when no card")</span><br><span style="color: hsl(120, 100%, 40%);">+               print(" is inserted.)")</span><br><span style="color: hsl(120, 100%, 40%);">+             print("")</span><br><span style="color: hsl(120, 100%, 40%);">+           app = PysimApp(None, None, opts.script)</span><br><span> </span><br><span>  # If the user supplies an ADM PIN at via commandline args authenticate</span><br><span>       # immediately so that the user does not have to use the shell commands</span><br><span>       pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)</span><br><span>   if pin_adm:</span><br><span style="color: hsl(120, 100%, 40%);">+           if not card:</span><br><span style="color: hsl(120, 100%, 40%);">+                  print("Card error, cannot do ADM verification with supplied ADM pin now.")</span><br><span>                 try:</span><br><span>                         card.verify_adm(h2b(pin_adm))</span><br><span>                except Exception as e:</span><br><span>diff --git a/pySim/filesystem.py b/pySim/filesystem.py</span><br><span>index fe781de..cc9176f 100644</span><br><span>--- a/pySim/filesystem.py</span><br><span>+++ b/pySim/filesystem.py</span><br><span>@@ -1329,6 +1329,11 @@</span><br><span>             raise TypeError("Only works with BER-TLV EF")</span><br><span>         return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    def unregister_cmds(self, cmd_app=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        """Unregister all file specific commands."""</span><br><span style="color: hsl(120, 100%, 40%);">+        if cmd_app and self.selected_file.shell_commands:</span><br><span style="color: hsl(120, 100%, 40%);">+            for c in self.selected_file.shell_commands:</span><br><span style="color: hsl(120, 100%, 40%);">+                cmd_app.unregister_command_set(c)</span><br><span> </span><br><span> </span><br><span> </span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/pysim/+/25549">change 25549</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/+/25549"/><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: I9cf532d9da8203065463c7201e7064de6c7ab1b5 </div>
<div style="display:none"> Gerrit-Change-Number: 25549 </div>
<div style="display:none"> Gerrit-PatchSet: 10 </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: dexter <pmaier@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: fixeria <vyanitskiy@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-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>