fastinit: escalate soft reset to physical on SW mismatch; retry/fallback

A probe can leave a card in a context where CLA-00 file access returns
6d00, so the software MF select fails (stock pysim init survives only
because it resets physically after probing). Fast init now recovers the
same way, on demand:

- FastRuntimeState.reset() falls back to hard_reset() on
  SwMatchError/ProtocolError (logged as FAST-RESET)
- init_card_fast() retries once after sl.reset_card() (FAST-INIT)
- __main__ falls back to stock pysim init once and skips
  TERMINAL PROFILE/drain when no card was initialized (no more
  6d00/6985 noise after a failed init)
- do_equip_fast() no longer pre-unregisters command sets; PysimApp.equip
  does that after a successful init, so a failed equip keeps the
  previous card/rs instead of leaving the app unequipped

Tests for the escalation, the retry and failed-equip state retention.
No card-model special cases.
This commit is contained in:
2026-09-12 13:02:33 +03:00
parent 25787017e5
commit 7288c22830
3 changed files with 118 additions and 9 deletions
+9 -2
View File
@@ -98,7 +98,12 @@ def main():
scc._tp.proactive_handler = _DefaultProactiveHandler()
t_phase = time.time()
if opts.fast_init:
rs, card = fastinit.init_card_fast(sl, opts.skip_card_init, wait=True)
try:
rs, card = fastinit.init_card_fast(sl, opts.skip_card_init, wait=True)
except Exception:
print("Warning: fast card initialization failed, falling back to pysim init:", file=sys.stderr)
traceback.print_exc()
rs, card = mod.init_card(sl, opts.skip_card_init)
else:
sl.wait_for_card(3)
rs, card = mod.init_card(sl, opts.skip_card_init)
@@ -118,7 +123,7 @@ def main():
_tlog('pysim_app: %.0fms' % ((time.time() - t_phase) * 1000))
if app is not None and opts.fast_init:
fastinit.install(app)
if scc and hasattr(scc, '_tp'):
if scc and card is not None and hasattr(scc, '_tp'):
scc._tp.apdu_tracer = _LoggingApduTracer()
try:
_init_proactive_session()
@@ -137,6 +142,8 @@ def main():
_tlog('terminal_profile_drain: %.0fms' % ((time.time() - t_phase) * 1000))
except Exception:
traceback.print_exc(file=sys.stderr)
elif scc is not None:
sys.stderr.write('INIT: card not initialized — use Equip once the card is readable\n')
if app is not None and opts.apdu_trace:
# PysimApp.__init__ routes PySimLogger through app.poutput() (app.stdout)
# and drops the root level to INFO. Re-route pysim's own APDU trace logging
+22 -7
View File
@@ -15,10 +15,11 @@ keep a real reconnect/physical reset.
"""
import operator
import sys
from pySim.cards import CardBase, SimCardBase, UiccCardBase, card_detect
from pySim.commands import SimCardCommands
from pySim.exceptions import SwMatchError
from pySim.exceptions import ProtocolError, SwMatchError
from pySim.filesystem import CardApplication, CardModel
from pySim.profile import CardProfile
from pySim.runtime import RuntimeState
@@ -35,7 +36,11 @@ class FastRuntimeState(RuntimeState):
of power-cycling the card. Use hard_reset() for an explicit reset."""
def reset(self, cmd_app=None):
return self.soft_reset(cmd_app)
try:
return self.soft_reset(cmd_app)
except (SwMatchError, ProtocolError) as e:
sys.stderr.write('FAST-RESET: soft reset failed (%s), falling back to physical reset\n' % e)
return self.hard_reset(cmd_app)
def soft_reset(self, cmd_app=None):
for lchan_nr in list(self.lchan.keys()):
@@ -79,7 +84,18 @@ def init_card_fast(sl, skip_card_init=False, wait=True):
"""Replacement for pySim.app.init_card() that avoids redundant resets.
``wait`` performs the single disconnect/connect of this init (explicit
equip passes True; startup already connects via wait_for_card)."""
equip passes True; startup already connects via wait_for_card). If probing
leaves the card in a state the software reset cannot clear, retry once
after a physical reset."""
try:
return _init_card_once(sl, skip_card_init, wait)
except (SwMatchError, ProtocolError) as e:
sys.stderr.write('FAST-INIT: %s; retrying after physical reset\n' % e)
sl.reset_card()
return _init_card_once(sl, skip_card_init, wait=False)
def _init_card_once(sl, skip_card_init, wait):
scc = SimCardCommands(transport=sl)
if wait:
sl.wait_for_card(3)
@@ -127,10 +143,9 @@ def init_card_fast(sl, skip_card_init=False, wait=True):
def do_equip_fast(app):
"""Explicit equip: one real reconnect (wait_for_card) then reset-free init."""
if app.rs and app.rs.profile:
for cmd_set in app.rs.profile.shell_cmdsets:
app.unregister_command_set(cmd_set)
"""Explicit equip: one real reconnect (wait_for_card) then reset-free init.
PysimApp.equip() unregisters the old command sets itself after the new init
succeeds, so a failed init leaves the previous card state intact."""
rs, card = init_card_fast(app.sl, wait=True)
app.equip(card, rs)