Files
simple/tests/test_netstate_server.py
T
catarrh 0231df905c 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.
2026-10-24 18:54:31 +03:00

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()