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:
@@ -509,13 +509,15 @@ def parse_switch_response(action, data_hex):
|
|||||||
'message': _error_text(result)}
|
'message': _error_text(result)}
|
||||||
|
|
||||||
|
|
||||||
def switch_profile(send_apdu, run_chain, action, iccid=None, isdp_aid=None,
|
def switch_profile(app, send_apdu, run_chain, action, iccid=None,
|
||||||
refresh=True):
|
isdp_aid=None, refresh=True):
|
||||||
"""Run an ES10c Enable/DisableProfile switch.
|
"""Run an ES10c Enable/DisableProfile switch on the ISD-R.
|
||||||
|
|
||||||
``send_apdu(apdu_hex) -> (data_hex, sw)`` performs one raw STORE DATA and
|
``send_apdu(apdu_hex) -> (data_hex, sw)`` performs one raw STORE DATA and
|
||||||
``run_chain(sw91)`` answers the proactive command(s) the card sends
|
``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
|
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
|
(SGP.22 v2.6 §5.7.16/§5.7.17 step 6) and the switch completes upon the
|
||||||
@@ -523,6 +525,8 @@ 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
|
OK: the STORE DATA is never retried (the mid-switch card answers 6985 to
|
||||||
the retry) and the caller re-initializes the card afterwards."""
|
the retry) and the caller re-initializes the card afterwards."""
|
||||||
apdu = build_switch_apdu(action, iccid, isdp_aid, refresh)
|
apdu = build_switch_apdu(action, iccid, isdp_aid, refresh)
|
||||||
|
_select_isdr(app)
|
||||||
|
try:
|
||||||
data, sw = send_apdu(apdu)
|
data, sw = send_apdu(apdu)
|
||||||
if sw == '9000':
|
if sw == '9000':
|
||||||
out = parse_switch_response(action, data)
|
out = parse_switch_response(action, data)
|
||||||
@@ -538,3 +542,5 @@ def switch_profile(send_apdu, run_chain, action, iccid=None, isdp_aid=None,
|
|||||||
'refresh_seen': refresh_seen}
|
'refresh_seen': refresh_seen}
|
||||||
return {'ok': False, 'result': 'undefinedError', 'sw': sw,
|
return {'ok': False, 'result': 'undefinedError', 'sw': sw,
|
||||||
'message': 'SW %s' % sw}
|
'message': 'SW %s' % sw}
|
||||||
|
finally:
|
||||||
|
_restore(app)
|
||||||
|
|||||||
@@ -3861,7 +3861,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
|||||||
_finish_pending_menu(self.server, self.server.scc)
|
_finish_pending_menu(self.server, self.server.scc)
|
||||||
cursor = _PROACTIVE_ENTRY_ID
|
cursor = _PROACTIVE_ENTRY_ID
|
||||||
resp = esim.switch_profile(
|
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),
|
lambda sw: _esim_refresh_chain(self.server, sw),
|
||||||
action, iccid=body.get('iccid'),
|
action, iccid=body.get('iccid'),
|
||||||
isdp_aid=body.get('isdp_aid'),
|
isdp_aid=body.get('isdp_aid'),
|
||||||
|
|||||||
+14
-2
@@ -239,9 +239,11 @@ class EsimTests(unittest.TestCase):
|
|||||||
self.assertEqual(out['result'], 'undefinedError')
|
self.assertEqual(out['result'], 'undefinedError')
|
||||||
|
|
||||||
def test_switch_profile_parses_ok_response(self):
|
def test_switch_profile_parses_ok_response(self):
|
||||||
|
app, lchan = make_app()
|
||||||
sent = []
|
sent = []
|
||||||
resp = EnableProfileResp(children=[EnableResult(decoded='ok')])
|
resp = EnableProfileResp(children=[EnableResult(decoded='ok')])
|
||||||
out = esim.switch_profile(
|
out = esim.switch_profile(
|
||||||
|
app,
|
||||||
lambda apdu: sent.append(apdu) or (resp.to_tlv().hex(), '9000'),
|
lambda apdu: sent.append(apdu) or (resp.to_tlv().hex(), '9000'),
|
||||||
lambda sw: self.fail('no chain expected'),
|
lambda sw: self.fail('no chain expected'),
|
||||||
'enable', iccid='8970119000004002667')
|
'enable', iccid='8970119000004002667')
|
||||||
@@ -249,10 +251,15 @@ class EsimTests(unittest.TestCase):
|
|||||||
self.assertEqual(out['result'], 'ok')
|
self.assertEqual(out['result'], 'ok')
|
||||||
self.assertFalse(out['refresh_seen'])
|
self.assertFalse(out['refresh_seen'])
|
||||||
self.assertEqual(len(sent), 1)
|
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):
|
def test_switch_profile_treats_91xx_as_ok_and_runs_the_chain(self):
|
||||||
|
app, lchan = make_app()
|
||||||
chain = []
|
chain = []
|
||||||
out = esim.switch_profile(
|
out = esim.switch_profile(
|
||||||
|
app,
|
||||||
lambda apdu: ('', '9111'),
|
lambda apdu: ('', '9111'),
|
||||||
lambda sw: chain.append(sw) or True,
|
lambda sw: chain.append(sw) or True,
|
||||||
'disable', iccid='8970119000004002667')
|
'disable', iccid='8970119000004002667')
|
||||||
@@ -260,17 +267,22 @@ class EsimTests(unittest.TestCase):
|
|||||||
self.assertEqual(out['result'], 'ok')
|
self.assertEqual(out['result'], 'ok')
|
||||||
self.assertTrue(out['refresh_seen'])
|
self.assertTrue(out['refresh_seen'])
|
||||||
self.assertEqual(chain, ['9111'])
|
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):
|
def test_switch_profile_chain_failure_keeps_the_accepted_switch(self):
|
||||||
|
app, _ = make_app()
|
||||||
|
|
||||||
def boom(sw):
|
def boom(sw):
|
||||||
raise RuntimeError('fetch failed')
|
raise RuntimeError('fetch failed')
|
||||||
out = esim.switch_profile(lambda apdu: ('', '910f'), boom,
|
out = esim.switch_profile(app, lambda apdu: ('', '910f'), boom,
|
||||||
'disable', iccid='8970119000004002667')
|
'disable', iccid='8970119000004002667')
|
||||||
self.assertTrue(out['ok'])
|
self.assertTrue(out['ok'])
|
||||||
self.assertFalse(out['refresh_seen'])
|
self.assertFalse(out['refresh_seen'])
|
||||||
|
|
||||||
def test_switch_profile_reports_error_sw(self):
|
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'),
|
lambda sw: self.fail('no chain expected'),
|
||||||
'disable', iccid='8970119000004002667')
|
'disable', iccid='8970119000004002667')
|
||||||
self.assertFalse(out['ok'])
|
self.assertFalse(out['ok'])
|
||||||
|
|||||||
Reference in New Issue
Block a user