From 99183acc8953228b7b8c224239962fb2778cbcad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 19 Sep 2026 22:53:24 +0300 Subject: [PATCH] net-sim: skip files missing on the card; /api/write accepts a full path Live-testing against a UICC without the EPS files (no USIM service 85) and without the Kc files showed the runner aborting when EPSLOCI/Kc were absent. write_binary/write_record now take an 'optional' flag: a missing candidate file is logged as a skip step instead of failing the scenario (store_epsnsc, real/dummy locations, churn and Kc writes). Also allow POST /api/write to select by 'path' like /api/select and /api/read (the netsim live test needed it to restore the captured file values). --- docs/api.md | 7 +++++ pysim_simple_server/netsim.py | 49 +++++++++++++++++++++++++---------- pysim_simple_server/server.py | 8 ++++-- tests/test_netsim.py | 18 +++++++++++++ 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/docs/api.md b/docs/api.md index 2a62547..b049553 100644 --- a/docs/api.md +++ b/docs/api.md @@ -350,6 +350,13 @@ Write raw hex data to a file. {"name": "EF.ICCID", "fid": "2FE2", "data": "A0A1A2...", "parent_path": ["MF"]} ``` +A full path may be used instead of name/fid/parent, like `/api/select` and +`/api/read`: + +```json +{"path": "ADF.USIM/6F7E", "data": "4FE69DE7..."} +``` + For record files: ```json {"name": "EF.ADN", "fid": "6F3A", "data": "A0A1...", "record_nr": 1, "parent_path": ["MF", "7F10"]} diff --git a/pysim_simple_server/netsim.py b/pysim_simple_server/netsim.py index 87afb9f..06a7605 100644 --- a/pysim_simple_server/netsim.py +++ b/pysim_simple_server/netsim.py @@ -405,8 +405,14 @@ class NetSimRunner: raise StepError('%s returned SW %s' % (kw.get('action'), sw)) return step - def write_binary(self, key, data_hex, pad=True, label=None): - path = self._open(key) + def write_binary(self, key, data_hex, pad=True, label=None, optional=False): + try: + path = self._open(key) + except StepError as e: + if optional: + self._add('skip', file=label or key, note=str(e)) + return None + raise size = self.lchan.selected_file_size() data = data_hex if pad and size: @@ -415,8 +421,14 @@ class NetSimRunner: return self._check(data, sw, action='update_binary', file=label or key, path=path) - def write_record(self, key, data_hex, record=1, pad=True, label=None): - path = self._open(key) + def write_record(self, key, data_hex, record=1, pad=True, label=None, optional=False): + try: + path = self._open(key) + except StepError as e: + if optional: + self._add('skip', file=label or key, note=str(e)) + return None + raise size = self.lchan.selected_file_record_len() data = data_hex if pad and size: @@ -473,29 +485,33 @@ class NetSimRunner: self.kasme, _hexint(self.p('ul', 0)), _hexint(self.p('dl', 0)), _hexint(self.p('algo', '02'), 0x02)), - label='epsnsc') + label='epsnsc', optional=True) def write_real_locations(self, status=ST_UPDATED): self.write_binary('loci', build_loci( self.p('tmsi') or rand_hex(4), self.plmn, self.lac, status), - label='loci') + label='loci', optional=True) self.write_binary('psloci', build_psloci( self.p('ptmsi') or rand_hex(4), self.p('ptmsi_sig') or rand_hex(3), - self.plmn, self.lac, self.rac, status), label='psloci') + self.plmn, self.lac, self.rac, status), label='psloci', optional=True) self.write_binary('epsloci', build_epsloci( self.p('guti') or rand_hex(12), self.plmn, self.tac, status), - label='epsloci') + label='epsloci', optional=True) def write_dummy_locations(self, status=ST_NOT_UPDATED): - self.write_binary('loci', build_loci_dummy(self.plmn), label='loci') - self.write_binary('psloci', build_psloci_dummy(self.plmn), label='psloci') - self.write_binary('epsloci', build_epsloci_dummy(self.plmn), label='epsloci') + self.write_binary('loci', build_loci_dummy(self.plmn), label='loci', + optional=True) + self.write_binary('psloci', build_psloci_dummy(self.plmn), label='psloci', + optional=True) + self.write_binary('epsloci', build_epsloci_dummy(self.plmn), + label='epsloci', optional=True) def invalidate_kc(self): for key in ('kc', 'kcgprs'): try: self._open(key) - except StepError: + except StepError as e: + self._add('skip', file=key, note=str(e)) continue size = self.lchan.selected_file_size() or 9 self.write_binary(key, build_kc_invalidate(size), pad=False, @@ -507,7 +523,8 @@ class NetSimRunner: for key in ('kc', 'kcgprs'): try: self._open(key) - except StepError: + except StepError as e: + self._add('skip', file=key, note=str(e)) continue size = self.lchan.selected_file_size() or 9 self.write_binary(key, build_kc(kc, algo, size), pad=False, @@ -569,7 +586,11 @@ class NetSimRunner: self.kasme, _hexint(self.p('ul', 0)), _hexint(self.p('dl', 0)), _hexint(self.p('algo', '02'), 0x02)) - self._open('epsnsc') + try: + self._open('epsnsc') + except StepError as e: + self._add('skip', file='epsnsc', note=str(e)) + return size = self.lchan.selected_file_record_len() or 54 invalid = build_epsnsc_invalidate(size, self.kasme) for _i in range(max(1, count)): diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 7e9ef32..99ee5d2 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -3706,6 +3706,7 @@ class PysimHandler(BaseHTTPRequestHandler): parent_sel = body.get('parent_sel') parent_path = body.get('parent_path') allow_probe = bool(body.get('allow_probe')) + path = body.get('path') rs = app.rs if not rs: self._send_json({'error': _err('no_card_state', lang)}, 503) @@ -3714,8 +3715,11 @@ class PysimHandler(BaseHTTPRequestHandler): lchan = rs.lchan[0] cleanup = None try: - sel = fid if fid else name - _, cleanup = _select_with_parent(lchan, sel, parent_sel, app, parent_path, allow_probe) + if path: + _, cleanup = _select_path(lchan, path, app) + else: + sel = fid if fid else name + _, cleanup = _select_with_parent(lchan, sel, parent_sel, app, parent_path, allow_probe) ft = _get_file_type(lchan, lchan.selected_file) is_record = ft in ('linear_fixed', 'cyclic') if record_nr: diff --git a/tests/test_netsim.py b/tests/test_netsim.py index dc2efa5..377806c 100644 --- a/tests/test_netsim.py +++ b/tests/test_netsim.py @@ -310,6 +310,24 @@ class RunnerTests(unittest.TestCase): with self.assertRaises(ValueError): runner.run('nope') + def test_missing_files_are_skipped_not_fatal(self): + # A card without the EPS files / Kc (e.g. no USIM service 85): the + # scenario must still succeed, writing what exists and noting skips. + files = {'6F7E': FakeFileInfo(size=11), '6F73': FakeFileInfo(size=14), + '6F43': FakeFileInfo(size=2, data='27FF')} + lchan = FakeLchan(files) + app = SimpleNamespace(rs=SimpleNamespace(lchan=[lchan])) + srv = FakeSrv() + runner = netsim.NetSimRunner(srv, app, event_list=[3], sleep=lambda s: None) + out = runner.run('service_lost') + self.assertTrue(out['success']) + skipped = {s['file'] for s in out['steps'] if s['action'] == 'skip'} + for key in ('epsnsc', 'epsloci', 'kc', 'kcgprs'): + self.assertIn(key, skipped) + written = [w[1] for w in lchan.writes] + self.assertIn('6F7E', written) + self.assertIn('6F73', written) + if __name__ == '__main__': unittest.main()