fix: select the ISD-R before the profile switch command
The switch refactor dropped the ISD-R selection that the old
_transceive/_run wrapper performed, so the STORE DATA went to whatever
application was selected (MF after a preceding ES10 call) and the card
answered 6D00 ('instruction not supported') without ever reaching the
switch flow.
- esim.switch_profile() now takes the app, selects the ISD-R before
sending the raw STORE DATA and restores the selection in a finally,
like the other ES10 functions.
- tests assert the selection and the restore for the 9000 and 91XX
paths (the FakeLchan/rs already track both).
This commit is contained in:
+25
-19
@@ -509,13 +509,15 @@ def parse_switch_response(action, data_hex):
|
||||
'message': _error_text(result)}
|
||||
|
||||
|
||||
def switch_profile(send_apdu, run_chain, action, iccid=None, isdp_aid=None,
|
||||
refresh=True):
|
||||
"""Run an ES10c Enable/DisableProfile switch.
|
||||
def switch_profile(app, send_apdu, run_chain, action, iccid=None,
|
||||
isdp_aid=None, refresh=True):
|
||||
"""Run an ES10c Enable/DisableProfile switch on the ISD-R.
|
||||
|
||||
``send_apdu(apdu_hex) -> (data_hex, sw)`` performs one raw STORE DATA and
|
||||
``run_chain(sw91)`` answers the proactive command(s) the card sends
|
||||
alongside the switch (True when a REFRESH was answered).
|
||||
alongside the switch (True when a REFRESH was answered). The ISD-R is
|
||||
selected first and the previous selection restored afterwards, like the
|
||||
other ES10 functions.
|
||||
|
||||
With the refresh flag set the ISD-R returns OK *before* the REFRESH
|
||||
(SGP.22 v2.6 §5.7.16/§5.7.17 step 6) and the switch completes upon the
|
||||
@@ -523,18 +525,22 @@ def switch_profile(send_apdu, run_chain, action, iccid=None, isdp_aid=None,
|
||||
OK: the STORE DATA is never retried (the mid-switch card answers 6985 to
|
||||
the retry) and the caller re-initializes the card afterwards."""
|
||||
apdu = build_switch_apdu(action, iccid, isdp_aid, refresh)
|
||||
data, sw = send_apdu(apdu)
|
||||
if sw == '9000':
|
||||
out = parse_switch_response(action, data)
|
||||
out['refresh_seen'] = False
|
||||
return out
|
||||
if sw and sw.startswith('91'):
|
||||
refresh_seen = False
|
||||
try:
|
||||
refresh_seen = bool(run_chain(sw))
|
||||
except Exception as e:
|
||||
sys.stderr.write('ESIM: REFRESH chain failed: %s\n' % e)
|
||||
return {'ok': True, 'result': 'ok', 'message': 'ok',
|
||||
'refresh_seen': refresh_seen}
|
||||
return {'ok': False, 'result': 'undefinedError', 'sw': sw,
|
||||
'message': 'SW %s' % sw}
|
||||
_select_isdr(app)
|
||||
try:
|
||||
data, sw = send_apdu(apdu)
|
||||
if sw == '9000':
|
||||
out = parse_switch_response(action, data)
|
||||
out['refresh_seen'] = False
|
||||
return out
|
||||
if sw and sw.startswith('91'):
|
||||
refresh_seen = False
|
||||
try:
|
||||
refresh_seen = bool(run_chain(sw))
|
||||
except Exception as e:
|
||||
sys.stderr.write('ESIM: REFRESH chain failed: %s\n' % e)
|
||||
return {'ok': True, 'result': 'ok', 'message': 'ok',
|
||||
'refresh_seen': refresh_seen}
|
||||
return {'ok': False, 'result': 'undefinedError', 'sw': sw,
|
||||
'message': 'SW %s' % sw}
|
||||
finally:
|
||||
_restore(app)
|
||||
|
||||
@@ -3861,7 +3861,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
_finish_pending_menu(self.server, self.server.scc)
|
||||
cursor = _PROACTIVE_ENTRY_ID
|
||||
resp = esim.switch_profile(
|
||||
self.server.scc._tp.send_apdu,
|
||||
app, self.server.scc._tp.send_apdu,
|
||||
lambda sw: _esim_refresh_chain(self.server, sw),
|
||||
action, iccid=body.get('iccid'),
|
||||
isdp_aid=body.get('isdp_aid'),
|
||||
|
||||
+14
-2
@@ -239,9 +239,11 @@ class EsimTests(unittest.TestCase):
|
||||
self.assertEqual(out['result'], 'undefinedError')
|
||||
|
||||
def test_switch_profile_parses_ok_response(self):
|
||||
app, lchan = make_app()
|
||||
sent = []
|
||||
resp = EnableProfileResp(children=[EnableResult(decoded='ok')])
|
||||
out = esim.switch_profile(
|
||||
app,
|
||||
lambda apdu: sent.append(apdu) or (resp.to_tlv().hex(), '9000'),
|
||||
lambda sw: self.fail('no chain expected'),
|
||||
'enable', iccid='8970119000004002667')
|
||||
@@ -249,10 +251,15 @@ class EsimTests(unittest.TestCase):
|
||||
self.assertEqual(out['result'], 'ok')
|
||||
self.assertFalse(out['refresh_seen'])
|
||||
self.assertEqual(len(sent), 1)
|
||||
# the ISD-R was selected for the command and the selection restored
|
||||
self.assertEqual(lchan.selected.name, 'ISD-R')
|
||||
self.assertEqual(app.rs.resets, 1)
|
||||
|
||||
def test_switch_profile_treats_91xx_as_ok_and_runs_the_chain(self):
|
||||
app, lchan = make_app()
|
||||
chain = []
|
||||
out = esim.switch_profile(
|
||||
app,
|
||||
lambda apdu: ('', '9111'),
|
||||
lambda sw: chain.append(sw) or True,
|
||||
'disable', iccid='8970119000004002667')
|
||||
@@ -260,17 +267,22 @@ class EsimTests(unittest.TestCase):
|
||||
self.assertEqual(out['result'], 'ok')
|
||||
self.assertTrue(out['refresh_seen'])
|
||||
self.assertEqual(chain, ['9111'])
|
||||
self.assertEqual(lchan.selected.name, 'ISD-R')
|
||||
self.assertEqual(app.rs.resets, 1)
|
||||
|
||||
def test_switch_profile_chain_failure_keeps_the_accepted_switch(self):
|
||||
app, _ = make_app()
|
||||
|
||||
def boom(sw):
|
||||
raise RuntimeError('fetch failed')
|
||||
out = esim.switch_profile(lambda apdu: ('', '910f'), boom,
|
||||
out = esim.switch_profile(app, lambda apdu: ('', '910f'), boom,
|
||||
'disable', iccid='8970119000004002667')
|
||||
self.assertTrue(out['ok'])
|
||||
self.assertFalse(out['refresh_seen'])
|
||||
|
||||
def test_switch_profile_reports_error_sw(self):
|
||||
out = esim.switch_profile(lambda apdu: ('', '6985'),
|
||||
app, _ = make_app()
|
||||
out = esim.switch_profile(app, lambda apdu: ('', '6985'),
|
||||
lambda sw: self.fail('no chain expected'),
|
||||
'disable', iccid='8970119000004002667')
|
||||
self.assertFalse(out['ok'])
|
||||
|
||||
Reference in New Issue
Block a user