<p>dexter has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.osmocom.org/c/pysim/+/25549">View Change</a></p><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, 84 insertions(+), 21 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/49/25549/1</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 92b2e02..fb9a894 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>@@ -92,28 +93,63 @@</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 style="color: hsl(120, 100%, 40%);">+         basic_commands = []</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(0, 100%, 40%);">-            self.py_locals = { 'card': self.card, 'rs' : self.rs }</span><br><span>               self.numeric_path = False</span><br><span style="color: hsl(0, 100%, 40%);">-               self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',</span><br><span style="color: hsl(0, 100%, 40%);">-                                                  onchange_cb=self._onchange_numeric_path))</span><br><span>          self.conserve_write = True</span><br><span style="color: hsl(0, 100%, 40%);">-              self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',</span><br><span style="color: hsl(0, 100%, 40%);">-                                                  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 style="color: hsl(0, 100%, 40%);">-           self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output'))</span><br><span>              self.apdu_trace = False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',</span><br><span style="color: hsl(120, 100%, 40%);">+                                              onchange_cb=self._onchange_numeric_path))</span><br><span style="color: hsl(120, 100%, 40%);">+             self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output'))</span><br><span>              self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',</span><br><span style="color: hsl(0, 100%, 40%);">-                                                onchange_cb=self._onchange_apdu_trace))</span><br><span style="color: hsl(120, 100%, 40%);">+                                             onchange_cb=self._onchange_apdu_trace))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             # Ensure we have a card and rs attribute</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 style="color: hsl(120, 100%, 40%);">+                self.py_locals = { 'card': self.card, 'rs' : self.rs }</span><br><span style="color: hsl(120, 100%, 40%);">+</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%);">+         if 'conserve_write' in self.settables:</span><br><span style="color: hsl(120, 100%, 40%);">+                        self.remove_settable('conserve_write')</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.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.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',</span><br><span style="color: hsl(120, 100%, 40%);">+                                                      onchange_cb=self._onchange_conserve_write))</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> </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>@@ -144,14 +180,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>@@ -533,16 +578,29 @@</span><br><span>   scc = SimCardCommands(transport=sl)</span><br><span> </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 == None or card == 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 initalize 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 initalization failed with an execption:")</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 170429b..ea1fd81 100644</span><br><span>--- a/pySim/filesystem.py</span><br><span>+++ b/pySim/filesystem.py</span><br><span>@@ -1324,6 +1324,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: 1 </div>
<div style="display:none"> Gerrit-Owner: dexter <pmaier@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>