equip/init: read the selected EF directly, bound the startup drain

pySim's scc.read_binary/read_record call select_path(ef) first, so the
ICCID, network-state, EF.ARR and EF.PBR readers - which had already made a
parent-scoped selection and hold its FCP - sent a second SELECT per file
and per record.  _read_selected_ef() issues READ BINARY (chunked at
max_cmd_len from the FCP file size) / READ RECORD (b2<rec>04<len> at the
FCP record length) directly; the pySim helpers stay as the fallback when
the FCP lacks the value.

The startup TERMINAL PROFILE drain now breaks on the chain's idle SW
instead of polling STATUS once more.

Measured on the last startup: ~3.4 s traced init (netstate/ARR/PBR 271
APDUs / 1.26 s incl. 169 SELECTs; the card's own TP processing 1.58 s);
the change removes ~0.7-0.8 s of the read phase without touching any data
or the TP.

v3.38.1; tests: tests/test_read_selected.py (direct reads, chunking, the
fallback and the drain source pin) and the ARR/PBR/ICCID/netstate fakes.
1067 Python + 1075 frontend.  sw cache simple-v482.
This commit is contained in:
catarrh committed 2026-10-24 18:54:31 +03:00
1 parent 80cbd8e935
commit 0231df905c
11 files changed
+252 -20

No files matched your search

+21 -5
View File
@@ -23,13 +23,29 @@ class FakeFile:
self.fid = fid
class FakeScc:
"""Serves READ RECORD for the selected fake ARR file."""
cla_byte = '00'
max_cmd_len = 255
def __init__(self, lchan):
self.lchan = lchan
self.sent = []
def send_apdu_checksw(self, pdu):
self.sent.append(pdu)
return (self.lchan.files[self.lchan._cur][int(pdu[4:6], 16) - 1], '9000')
class FakeLchan:
"""One selected ARR file: read_record serves its records."""
"""One selected ARR file: READ RECORD serves its records."""
def __init__(self, files):
self.files = files # path -> [record hex]
self._cur = None
self.selected_file = None
self.scc = FakeScc(self)
def selected_file_num_of_rec(self):
return len(self.files.get(self._cur) or [])
@@ -96,13 +112,13 @@ class ReadArrTests(unittest.TestCase):
def test_a_failing_read_skips_the_file(self):
app, lchan = _app({'MF/2F06': ['9000'], 'ADF.USIM/6F06': ['9000']})
orig = lchan.read_record
orig = lchan.scc.send_apdu_checksw
def flaky(n):
def flaky(pdu):
if lchan._cur == 'MF/2F06':
raise RuntimeError('6A82')
return orig(n)
lchan.read_record = flaky
return orig(pdu)
lchan.scc.send_apdu_checksw = flaky
arrs = _read(app)
self.assertEqual([a['fid'] for a in arrs], ['6F06'])
+21
View File
@@ -32,6 +32,21 @@ class FakeFile:
self.applications = {}
class FakeScc:
cla_byte = '00'
max_cmd_len = 255
def __init__(self, lchan):
self.lchan = lchan
self.sent = []
def send_apdu_checksw(self, pdu):
self.sent.append(pdu)
if self.lchan.error:
raise self.lchan.error
return self.lchan.data, '9000'
class FakeLchan:
def __init__(self, mf, data=None, error=None):
self.selected_file = mf
@@ -39,6 +54,10 @@ class FakeLchan:
self.error = error
self.selects = []
self.probes = []
self.scc = FakeScc(self)
def selected_file_size(self):
return len(self.data or '') // 2
def select_file(self, f, app=None):
self.selected_file = f
@@ -94,6 +113,8 @@ class ReadIccidTest(unittest.TestCase):
self.assertIs(lchan.selected_file, iccid)
self.assertEqual([f.name for f in lchan.selects], ['MF', 'EF.ICCID'])
self.assertEqual(lchan.probes, [])
# one READ BINARY, no second SELECT (v3.38.1)
self.assertEqual(lchan.scc.sent, ['00b000000a'])
def test_missing_model_entry_is_probed_and_detached(self):
app, lchan, mf, _ = make_app(with_iccid=False)
+27
View File
@@ -13,6 +13,28 @@ from types import SimpleNamespace
from pysim_simple_server import server
class FakeScc:
"""Serves the selected fake file for direct READ BINARY/RECORD APDUs."""
cla_byte = '00'
max_cmd_len = 255
def __init__(self, lchan):
self.lchan = lchan
self.sent = []
def send_apdu_checksw(self, pdu):
self.sent.append(pdu)
kind, val = self.lchan.files[self.lchan.selected]
if pdu[2:4] == 'b0':
off = int(pdu[4:8], 16)
ln = int(pdu[8:10], 16)
return val[off * 2:(off + ln) * 2], '9000'
if pdu[2:4] == 'b2':
return val[int(pdu[4:6], 16) - 1], '9000'
raise AssertionError('unexpected APDU %s' % pdu)
class FakeLchan:
def __init__(self, files):
self.files = files # path -> ('transparent', hex) | ('record', [hex])
@@ -23,6 +45,9 @@ class FakeLchan:
self.selected_file_type = lambda: 'ef'
self.selected_file_structure = lambda: self.files[self.selected][0]
self.selected_file_num_of_rec = lambda: len(self.files[self.selected][1])
self.selected_file_record_len = lambda: 2
self.selected_file_size = lambda: len(self.files[self.selected][1]) // 2
self.scc = FakeScc(self)
self.read_binary = lambda: (self.files[self.selected][1], '9000')
self.read_record = lambda i: (self.files[self.selected][1][i - 1], '9000')
@@ -68,6 +93,8 @@ class NetStateServerTests(unittest.TestCase):
('select', 'DF.GSM/6F07'),
('select', 'ADF.USIM/6FCD'),
('select', 'ADF.USIM/6FE4')])
# v3.38.1: the reads use the selected file directly - no second SELECT
self.assertEqual(lchan.scc.sent, ['00b0000003', '00b2010402', '00b2020402'])
def test_install_builds_the_state_and_network(self):
files = {'imsi': {'name': 'EF.IMSI', 'fid': '6F07', 'present': True,
+21 -5
View File
@@ -34,13 +34,29 @@ class FakeFile:
self.fid = fid
class FakeScc:
"""Serves READ RECORD for the selected fake PBR file."""
cla_byte = '00'
max_cmd_len = 255
def __init__(self, lchan):
self.lchan = lchan
self.sent = []
def send_apdu_checksw(self, pdu):
self.sent.append(pdu)
return (self.lchan.files[self.lchan._cur][int(pdu[4:6], 16) - 1], '9000')
class FakeLchan:
"""One selected PBR file: read_record serves its records."""
"""One selected PBR file: READ RECORD serves its records."""
def __init__(self, files):
self.files = files # path -> [record hex]
self._cur = None
self.selected_file = None
self.scc = FakeScc(self)
def selected_file_num_of_rec(self):
return len(self.files.get(self._cur) or [])
@@ -101,13 +117,13 @@ class ReadPbrTests(unittest.TestCase):
def test_a_failing_read_skips_the_file(self):
app, lchan = _app({'MF/7F10/5F3A/4F30': ['A80AC0034F3A02'],
'ADF.USIM/5F3A/4F30': ['A80AC0034F3A02']})
orig = lchan.read_record
orig = lchan.scc.send_apdu_checksw
def flaky(n):
def flaky(pdu):
if lchan._cur == 'MF/7F10/5F3A/4F30':
raise RuntimeError('6A82')
return orig(n)
lchan.read_record = flaky
return orig(pdu)
lchan.scc.send_apdu_checksw = flaky
pbrs = _read(app)
self.assertEqual([p['path'] for p in pbrs], ['ADF.USIM/5F3A/4F30'])
+93
View File
@@ -0,0 +1,93 @@
"""The direct read of an already-selected EF (v3.38.1).
pySim's ``scc.read_binary``/``scc.read_record`` select the file first; the
equip/init readers have already made a parent-scoped selection and hold its
FCP, so that second SELECT is pure overhead (one per file, one per record).
These tests pin the direct READ BINARY/READ RECORD form, the chunking and the
fallback when the FCP lacks the size/record length.
"""
import pathlib
import unittest
from pysim_simple_server import server as S
class FakeScc:
cla_byte = '00'
max_cmd_len = 255
def __init__(self, data='AABB'):
self.sent = []
self.data = data
def send_apdu_checksw(self, pdu):
self.sent.append(pdu)
return self.data, '9000'
class FakeLchan:
def __init__(self, size=2, rec_len=2, data='AABB'):
self.scc = FakeScc(data)
self.size = size
self.rec_len = rec_len
def selected_file_size(self):
return self.size
def selected_file_record_len(self):
return self.rec_len
def read_binary(self, *args):
raise AssertionError('pySim read_binary (re-select) used')
def read_record(self, *args):
raise AssertionError('pySim read_record (re-select) used')
class ReadSelectedTests(unittest.TestCase):
def test_a_transparent_read_is_direct(self):
lchan = FakeLchan(size=2)
self.assertEqual(S._read_selected_ef(lchan, 'transparent'),
('AABB', '9000'))
self.assertEqual(lchan.scc.sent, ['00b0000002'])
def test_a_transparent_file_is_chunked_at_max_cmd_len(self):
lchan = FakeLchan(size=300)
S._read_selected_ef(lchan, 'transparent')
# 255 bytes at offset 0, then 45 bytes at offset 0x00ff
self.assertEqual(lchan.scc.sent, ['00b00000ff', '00b000ff2d'])
def test_an_empty_file_reads_nothing(self):
lchan = FakeLchan(size=0)
self.assertEqual(S._read_selected_ef(lchan, 'transparent'),
('', '9000'))
self.assertEqual(lchan.scc.sent, [])
def test_a_record_read_is_direct(self):
lchan = FakeLchan(rec_len=6)
S._read_selected_ef(lchan, 'record', 3)
self.assertEqual(lchan.scc.sent, ['00b2030406'])
def test_a_missing_fcp_falls_back_to_the_pysim_helpers(self):
lchan = FakeLchan(size=None, rec_len=None)
lchan.read_binary = lambda *a: ('CC', '9000')
lchan.read_record = lambda n: ('DD', '9000')
self.assertEqual(S._read_selected_ef(lchan, 'transparent'),
('CC', '9000'))
self.assertEqual(S._read_selected_ef(lchan, 'record', 1),
('DD', '9000'))
class StartupDrainPin(unittest.TestCase):
"""The startup drain must not poll STATUS again after the chain already
returned an idle SW (v3.38.1)."""
def test_the_drain_uses_the_chain_result(self):
src = (pathlib.Path(S.__file__).parent / '__main__.py').read_text()
self.assertIn('chain_sw = _handle_proactive_chain(scc, st_sw)', src)
self.assertIn("if chain_sw and not chain_sw.startswith('91'):", src)
if __name__ == '__main__':
unittest.main()