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).
This commit is contained in:
@@ -350,6 +350,13 @@ Write raw hex data to a file.
|
|||||||
{"name": "EF.ICCID", "fid": "2FE2", "data": "A0A1A2...", "parent_path": ["MF"]}
|
{"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:
|
For record files:
|
||||||
```json
|
```json
|
||||||
{"name": "EF.ADN", "fid": "6F3A", "data": "A0A1...", "record_nr": 1, "parent_path": ["MF", "7F10"]}
|
{"name": "EF.ADN", "fid": "6F3A", "data": "A0A1...", "record_nr": 1, "parent_path": ["MF", "7F10"]}
|
||||||
|
|||||||
@@ -405,8 +405,14 @@ class NetSimRunner:
|
|||||||
raise StepError('%s returned SW %s' % (kw.get('action'), sw))
|
raise StepError('%s returned SW %s' % (kw.get('action'), sw))
|
||||||
return step
|
return step
|
||||||
|
|
||||||
def write_binary(self, key, data_hex, pad=True, label=None):
|
def write_binary(self, key, data_hex, pad=True, label=None, optional=False):
|
||||||
|
try:
|
||||||
path = self._open(key)
|
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()
|
size = self.lchan.selected_file_size()
|
||||||
data = data_hex
|
data = data_hex
|
||||||
if pad and size:
|
if pad and size:
|
||||||
@@ -415,8 +421,14 @@ class NetSimRunner:
|
|||||||
return self._check(data, sw, action='update_binary', file=label or key,
|
return self._check(data, sw, action='update_binary', file=label or key,
|
||||||
path=path)
|
path=path)
|
||||||
|
|
||||||
def write_record(self, key, data_hex, record=1, pad=True, label=None):
|
def write_record(self, key, data_hex, record=1, pad=True, label=None, optional=False):
|
||||||
|
try:
|
||||||
path = self._open(key)
|
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()
|
size = self.lchan.selected_file_record_len()
|
||||||
data = data_hex
|
data = data_hex
|
||||||
if pad and size:
|
if pad and size:
|
||||||
@@ -473,29 +485,33 @@ class NetSimRunner:
|
|||||||
self.kasme,
|
self.kasme,
|
||||||
_hexint(self.p('ul', 0)), _hexint(self.p('dl', 0)),
|
_hexint(self.p('ul', 0)), _hexint(self.p('dl', 0)),
|
||||||
_hexint(self.p('algo', '02'), 0x02)),
|
_hexint(self.p('algo', '02'), 0x02)),
|
||||||
label='epsnsc')
|
label='epsnsc', optional=True)
|
||||||
|
|
||||||
def write_real_locations(self, status=ST_UPDATED):
|
def write_real_locations(self, status=ST_UPDATED):
|
||||||
self.write_binary('loci', build_loci(
|
self.write_binary('loci', build_loci(
|
||||||
self.p('tmsi') or rand_hex(4), self.plmn, self.lac, status),
|
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.write_binary('psloci', build_psloci(
|
||||||
self.p('ptmsi') or rand_hex(4), self.p('ptmsi_sig') or rand_hex(3),
|
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.write_binary('epsloci', build_epsloci(
|
||||||
self.p('guti') or rand_hex(12), self.plmn, self.tac, status),
|
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):
|
def write_dummy_locations(self, status=ST_NOT_UPDATED):
|
||||||
self.write_binary('loci', build_loci_dummy(self.plmn), label='loci')
|
self.write_binary('loci', build_loci_dummy(self.plmn), label='loci',
|
||||||
self.write_binary('psloci', build_psloci_dummy(self.plmn), label='psloci')
|
optional=True)
|
||||||
self.write_binary('epsloci', build_epsloci_dummy(self.plmn), label='epsloci')
|
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):
|
def invalidate_kc(self):
|
||||||
for key in ('kc', 'kcgprs'):
|
for key in ('kc', 'kcgprs'):
|
||||||
try:
|
try:
|
||||||
self._open(key)
|
self._open(key)
|
||||||
except StepError:
|
except StepError as e:
|
||||||
|
self._add('skip', file=key, note=str(e))
|
||||||
continue
|
continue
|
||||||
size = self.lchan.selected_file_size() or 9
|
size = self.lchan.selected_file_size() or 9
|
||||||
self.write_binary(key, build_kc_invalidate(size), pad=False,
|
self.write_binary(key, build_kc_invalidate(size), pad=False,
|
||||||
@@ -507,7 +523,8 @@ class NetSimRunner:
|
|||||||
for key in ('kc', 'kcgprs'):
|
for key in ('kc', 'kcgprs'):
|
||||||
try:
|
try:
|
||||||
self._open(key)
|
self._open(key)
|
||||||
except StepError:
|
except StepError as e:
|
||||||
|
self._add('skip', file=key, note=str(e))
|
||||||
continue
|
continue
|
||||||
size = self.lchan.selected_file_size() or 9
|
size = self.lchan.selected_file_size() or 9
|
||||||
self.write_binary(key, build_kc(kc, algo, size), pad=False,
|
self.write_binary(key, build_kc(kc, algo, size), pad=False,
|
||||||
@@ -569,7 +586,11 @@ class NetSimRunner:
|
|||||||
self.kasme,
|
self.kasme,
|
||||||
_hexint(self.p('ul', 0)), _hexint(self.p('dl', 0)),
|
_hexint(self.p('ul', 0)), _hexint(self.p('dl', 0)),
|
||||||
_hexint(self.p('algo', '02'), 0x02))
|
_hexint(self.p('algo', '02'), 0x02))
|
||||||
|
try:
|
||||||
self._open('epsnsc')
|
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
|
size = self.lchan.selected_file_record_len() or 54
|
||||||
invalid = build_epsnsc_invalidate(size, self.kasme)
|
invalid = build_epsnsc_invalidate(size, self.kasme)
|
||||||
for _i in range(max(1, count)):
|
for _i in range(max(1, count)):
|
||||||
|
|||||||
@@ -3706,6 +3706,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
|||||||
parent_sel = body.get('parent_sel')
|
parent_sel = body.get('parent_sel')
|
||||||
parent_path = body.get('parent_path')
|
parent_path = body.get('parent_path')
|
||||||
allow_probe = bool(body.get('allow_probe'))
|
allow_probe = bool(body.get('allow_probe'))
|
||||||
|
path = body.get('path')
|
||||||
rs = app.rs
|
rs = app.rs
|
||||||
if not rs:
|
if not rs:
|
||||||
self._send_json({'error': _err('no_card_state', lang)}, 503)
|
self._send_json({'error': _err('no_card_state', lang)}, 503)
|
||||||
@@ -3714,6 +3715,9 @@ class PysimHandler(BaseHTTPRequestHandler):
|
|||||||
lchan = rs.lchan[0]
|
lchan = rs.lchan[0]
|
||||||
cleanup = None
|
cleanup = None
|
||||||
try:
|
try:
|
||||||
|
if path:
|
||||||
|
_, cleanup = _select_path(lchan, path, app)
|
||||||
|
else:
|
||||||
sel = fid if fid else name
|
sel = fid if fid else name
|
||||||
_, cleanup = _select_with_parent(lchan, sel, parent_sel, app, parent_path, allow_probe)
|
_, cleanup = _select_with_parent(lchan, sel, parent_sel, app, parent_path, allow_probe)
|
||||||
ft = _get_file_type(lchan, lchan.selected_file)
|
ft = _get_file_type(lchan, lchan.selected_file)
|
||||||
|
|||||||
@@ -310,6 +310,24 @@ class RunnerTests(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
runner.run('nope')
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user