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:
@@ -5,6 +5,7 @@ import sys
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
PROJECTS = Path(__file__).resolve().parents[2]
|
||||
PY_SIM = PROJECTS / 'pysim'
|
||||
@@ -14,6 +15,7 @@ if str(PY_SIM) not in sys.path:
|
||||
from pySim.exceptions import SwMatchError
|
||||
from pySim.ts_102_221 import CardProfileUICC
|
||||
|
||||
import pysim_otaman_server.fastinit as fastinit
|
||||
from pysim_otaman_server.fastinit import (
|
||||
FastRuntimeState,
|
||||
do_reset_fast,
|
||||
@@ -123,3 +125,88 @@ class TestDoResetFast(unittest.TestCase):
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
||||
class FlakyLchan:
|
||||
"""Lchan whose first MF select fails, as if probing left the card in a
|
||||
context the software reset cannot clear."""
|
||||
|
||||
def __init__(self):
|
||||
self.scc = types.SimpleNamespace(scp=object())
|
||||
self.selected_adf = 'SOMETHING'
|
||||
self.select_calls = 0
|
||||
self.selected = []
|
||||
|
||||
def select(self, path, cmd_app=None):
|
||||
self.select_calls += 1
|
||||
if self.select_calls == 1:
|
||||
raise SwMatchError('6d00', '9000')
|
||||
self.selected.append(path)
|
||||
|
||||
|
||||
class ResettableCard:
|
||||
def __init__(self):
|
||||
self.resets = 0
|
||||
self._scc = types.SimpleNamespace(get_atr=lambda: 'AABB')
|
||||
|
||||
def reset(self):
|
||||
self.resets += 1
|
||||
return 'AABB'
|
||||
|
||||
|
||||
class TestFastResetEscalation(unittest.TestCase):
|
||||
def make_rs(self):
|
||||
rs = FastRuntimeState.__new__(FastRuntimeState)
|
||||
rs.lchan = {0: FlakyLchan(), 1: types.SimpleNamespace(scc=types.SimpleNamespace(scp=None))}
|
||||
rs.adm_verified = True
|
||||
rs.card = ResettableCard()
|
||||
rs.identity = {}
|
||||
return rs
|
||||
|
||||
def test_soft_reset_escalates_to_physical(self):
|
||||
rs = self.make_rs()
|
||||
atr = rs.reset()
|
||||
self.assertEqual(atr, 'AABB')
|
||||
self.assertEqual(rs.card.resets, 1)
|
||||
self.assertEqual(rs.lchan[0].select_calls, 2)
|
||||
self.assertEqual(rs.lchan[0].selected, ['MF'])
|
||||
self.assertFalse(rs.adm_verified)
|
||||
self.assertNotIn(1, rs.lchan)
|
||||
|
||||
|
||||
class TestInitCardFastRetry(unittest.TestCase):
|
||||
def test_retries_once_after_physical_reset(self):
|
||||
calls = []
|
||||
|
||||
def once(sl, skip, wait):
|
||||
calls.append(wait)
|
||||
if len(calls) == 1:
|
||||
raise SwMatchError('6d00', '9000')
|
||||
return ('rs', 'card')
|
||||
|
||||
sl = types.SimpleNamespace(resets=0)
|
||||
|
||||
def reset_card():
|
||||
sl.resets += 1
|
||||
|
||||
sl.reset_card = reset_card
|
||||
with mock.patch.object(fastinit, '_init_card_once', side_effect=once):
|
||||
rs, card = fastinit.init_card_fast(sl, wait=True)
|
||||
self.assertEqual(calls, [True, False])
|
||||
self.assertEqual(sl.resets, 1)
|
||||
self.assertEqual((rs, card), ('rs', 'card'))
|
||||
|
||||
|
||||
class TestDoEquipFastFailure(unittest.TestCase):
|
||||
def test_failed_equip_keeps_previous_state(self):
|
||||
calls = []
|
||||
app = types.SimpleNamespace(
|
||||
sl=object(),
|
||||
rs=types.SimpleNamespace(profile=types.SimpleNamespace(shell_cmdsets=[object()])),
|
||||
unregister_command_set=lambda cs: calls.append('unregister'),
|
||||
equip=lambda card, rs: calls.append('equip'),
|
||||
)
|
||||
with mock.patch.object(fastinit, 'init_card_fast', side_effect=SwMatchError('6d00', '9000')):
|
||||
with self.assertRaises(SwMatchError):
|
||||
fastinit.do_equip_fast(app)
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
Reference in New Issue
Block a user