server: add --fast-init to skip redundant card resets
pysim's init_card() resets the card once per profile candidate in CardProfile.pick(), once in RuntimeState.__init__ and again in PysimApp.equip(); on typical readers each reconnect costs ~1.3s and a normal init/equip does 7-8 of them (measured: equip 9.85s, startup ~14.8s). fastinit.py mirrors pySim.app.init_card() with the resets removed: pick_profile_no_reset() runs all profile probes back-to-back on one connection, FastRuntimeState.reset() is a software reset (select MF, clear selected_adf/scp, ATR from the transport) and the equip/reset commands are routed through do_equip_fast (one reconnect via wait_for_card) and do_reset_fast (always a physical reset). Enabled with --fast-init; stock behavior remains the default. Tests for the reset-free pick, the soft reset and the explicit reset paths. Docs updated; SW cache v88 -> v89.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests for the reset-free fast initialization helpers."""
|
||||
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
PROJECTS = Path(__file__).resolve().parents[2]
|
||||
PY_SIM = PROJECTS / 'pysim'
|
||||
if str(PY_SIM) not in sys.path:
|
||||
sys.path.insert(0, str(PY_SIM))
|
||||
|
||||
from pySim.exceptions import SwMatchError
|
||||
from pySim.ts_102_221 import CardProfileUICC
|
||||
|
||||
from pysim_otaman_server.fastinit import (
|
||||
FastRuntimeState,
|
||||
do_reset_fast,
|
||||
pick_profile_no_reset,
|
||||
)
|
||||
|
||||
|
||||
class FakeScc:
|
||||
def __init__(self):
|
||||
self.sel_ctrl = '0004'
|
||||
self.cla_byte = '00'
|
||||
self.resets = 0
|
||||
self.selected = []
|
||||
|
||||
def reset_card(self):
|
||||
self.resets += 1
|
||||
|
||||
def select_file(self, fid):
|
||||
self.selected.append(fid)
|
||||
return ('', '9000')
|
||||
|
||||
def select_adf(self, aid):
|
||||
raise SwMatchError('6a82', '9000')
|
||||
|
||||
|
||||
class TestPickProfileNoReset(unittest.TestCase):
|
||||
def test_uicc_selected_without_any_reset(self):
|
||||
scc = FakeScc()
|
||||
profile = pick_profile_no_reset(scc)
|
||||
self.assertIsInstance(profile, CardProfileUICC)
|
||||
self.assertEqual(scc.resets, 0)
|
||||
self.assertIn('3f00', scc.selected)
|
||||
|
||||
def test_reset_card_restored_after_pick(self):
|
||||
scc = FakeScc()
|
||||
pick_profile_no_reset(scc)
|
||||
scc.reset_card()
|
||||
self.assertEqual(scc.resets, 1)
|
||||
|
||||
|
||||
class FakeLchan:
|
||||
def __init__(self):
|
||||
self.scc = types.SimpleNamespace(scp=object())
|
||||
self.selected_adf = 'SOMETHING'
|
||||
self.selected = []
|
||||
|
||||
def select(self, path, cmd_app=None):
|
||||
self.selected.append(path)
|
||||
|
||||
|
||||
class TestFastRuntimeStateSoftReset(unittest.TestCase):
|
||||
def make_rs(self):
|
||||
rs = FastRuntimeState.__new__(FastRuntimeState)
|
||||
rs.lchan = {0: FakeLchan(), 1: FakeLchan()}
|
||||
rs.adm_verified = True
|
||||
rs.card = types.SimpleNamespace(_scc=types.SimpleNamespace(get_atr=lambda: 'AABB'))
|
||||
rs.identity = {}
|
||||
return rs
|
||||
|
||||
def test_soft_reset_selects_mf_without_physical_reset(self):
|
||||
rs = self.make_rs()
|
||||
atr = rs.soft_reset()
|
||||
self.assertEqual(atr, 'AABB')
|
||||
self.assertEqual(rs.identity['ATR'], 'AABB')
|
||||
self.assertEqual(rs.lchan[0].selected, ['MF'])
|
||||
self.assertIsNone(rs.lchan[0].selected_adf)
|
||||
self.assertFalse(rs.adm_verified)
|
||||
self.assertNotIn(1, rs.lchan)
|
||||
|
||||
def test_reset_is_soft(self):
|
||||
rs = self.make_rs()
|
||||
rs.card = types.SimpleNamespace(_scc=types.SimpleNamespace(get_atr=lambda: 'EEFF'))
|
||||
self.assertEqual(rs.reset(), 'EEFF')
|
||||
self.assertEqual(rs.lchan[0].selected, ['MF'])
|
||||
|
||||
|
||||
class FakeCardScc:
|
||||
def __init__(self):
|
||||
self.resets = 0
|
||||
|
||||
def reset_card(self):
|
||||
self.resets += 1
|
||||
return 'ATR'
|
||||
|
||||
def get_atr(self):
|
||||
return 'AABB'
|
||||
|
||||
|
||||
class TestDoResetFast(unittest.TestCase):
|
||||
def test_explicit_reset_is_physical(self):
|
||||
scc = FakeCardScc()
|
||||
out = []
|
||||
app = types.SimpleNamespace(rs=None, card=types.SimpleNamespace(_scc=scc), poutput=out.append)
|
||||
do_reset_fast(app)
|
||||
self.assertEqual(scc.resets, 1)
|
||||
self.assertEqual(out, ['Card ATR: AABB'])
|
||||
|
||||
def test_explicit_reset_uses_hard_reset_with_runtime_state(self):
|
||||
calls = []
|
||||
rs = types.SimpleNamespace(hard_reset=lambda cmd_app=None: calls.append(cmd_app) or 'CCDD')
|
||||
out = []
|
||||
app = types.SimpleNamespace(rs=rs, card=None, poutput=out.append)
|
||||
do_reset_fast(app)
|
||||
self.assertEqual(calls, [app])
|
||||
self.assertEqual(out, ['Card ATR: CCDD'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user