<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>