0231df905c
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.
125 lines
4.9 KiB
Python
125 lines
4.9 KiB
Python
# coding=utf-8
|
|
"""Tests for the server-side Network-state monitor plumbing.
|
|
|
|
Covers the read helper (candidate order, transparent vs record files, absent
|
|
files) and the install/ensure helpers shared by the equip paths and the HTTP
|
|
endpoints. The card I/O is faked; ``_select_path`` is stubbed so no pySim
|
|
file tree is needed.
|
|
"""
|
|
|
|
import unittest
|
|
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])
|
|
self.calls = []
|
|
self.selected = None
|
|
self.selected_file = None
|
|
self.selected_file_fcp = True
|
|
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')
|
|
|
|
def select(self, path):
|
|
self.calls.append(('select', path))
|
|
if path not in self.files:
|
|
raise RuntimeError('file not found: %s' % path)
|
|
self.selected = path
|
|
|
|
|
|
class NetStateServerTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self._orig_select = server._select_path
|
|
|
|
def fake_select(lchan, path, app):
|
|
lchan.select(path)
|
|
return None, None
|
|
|
|
server._select_path = fake_select
|
|
|
|
def tearDown(self):
|
|
server._select_path = self._orig_select
|
|
|
|
def test_read_walks_candidates_and_marks_missing(self):
|
|
lchan = FakeLchan({
|
|
'DF.GSM/6F07': ('transparent', '0829AA'), # 2nd IMSI candidate
|
|
'ADF.USIM/6FE4': ('linear_fixed', ['AABB', 'CCDD']), # EPSNSC records
|
|
})
|
|
app = SimpleNamespace(rs=SimpleNamespace(lchan=[lchan]))
|
|
out = server._netstate_read(app, ['imsi', 'epsnsc', 'spdi'])
|
|
self.assertTrue(out['imsi']['present'])
|
|
self.assertEqual(out['imsi']['path'], 'DF.GSM/6F07')
|
|
self.assertEqual(out['imsi']['kind'], 'transparent')
|
|
self.assertEqual(out['imsi']['data'], '0829AA')
|
|
self.assertEqual(out['epsnsc']['kind'], 'record')
|
|
self.assertEqual(out['epsnsc']['records'],
|
|
[{'num': 1, 'data': 'AABB'},
|
|
{'num': 2, 'data': 'CCDD'}])
|
|
self.assertFalse(out['spdi']['present'])
|
|
# candidates are walked in FILE_DEFS order (spdi sits before epsnsc)
|
|
self.assertEqual(lchan.calls,
|
|
[('select', 'ADF.USIM/6F07'),
|
|
('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,
|
|
'kind': 'transparent', 'data': '082982608200002080'}}
|
|
srv = SimpleNamespace(mcc_mnc_path=None)
|
|
server._netstate_install(srv, files)
|
|
self.assertEqual(srv.net_state['files']['imsi']['source'], 'init')
|
|
self.assertEqual(srv.net_state['files']['imsi']['data'],
|
|
'082982608200002080')
|
|
self.assertIsNotNone(srv.net_state['read_at'])
|
|
self.assertIsNotNone(srv.net_state['network'])
|
|
|
|
def test_ensure_creates_the_state_only_with_a_readable_iccid(self):
|
|
lchan = FakeLchan({})
|
|
app = SimpleNamespace(rs=SimpleNamespace(lchan=[lchan]))
|
|
srv = SimpleNamespace(iccid='8970119000004600098', app=app,
|
|
mcc_mnc_path=None)
|
|
state = server._netstate_ensure(srv)
|
|
self.assertIsNotNone(state)
|
|
self.assertEqual(srv.net_state, state)
|
|
srv2 = SimpleNamespace(iccid=None, app=app, mcc_mnc_path=None)
|
|
self.assertIsNone(server._netstate_ensure(srv2))
|
|
self.assertFalse(hasattr(srv2, 'net_state'))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|