diff --git a/docs/shell.rst b/docs/shell.rst index 5bc86ae3..3d56c6d7 100644 --- a/docs/shell.rst +++ b/docs/shell.rst @@ -156,6 +156,20 @@ close_channel :func: Iso7816Commands.close_chan_parser +suspend_uicc +~~~~~~~~~~~~ +This command allows you to perform the SUSPEND UICC command on the card. This is a relatively +recent power-saving addition to the UICC specifications, allowing for suspend/resume while maintaining +state, as opposed to a full power-off (deactivate) and power-on (activate) of the card. + +The pySim command just sends that SUSPEND UICC command and doesn't perform the full related sequence +including the electrical power down. + +.. argparse:: + :module: pySim-shell + :func: Iso7816Commands.suspend_uicc_parser + + pySim commands -------------- diff --git a/pySim-shell.py b/pySim-shell.py index 4cf32a4e..ff123964 100755 --- a/pySim-shell.py +++ b/pySim-shell.py @@ -718,6 +718,20 @@ class Iso7816Commands(CommandSet): fcp_dec = self._cmd.rs.status() self._cmd.poutput_json(fcp_dec) + suspend_uicc_parser = argparse.ArgumentParser() + suspend_uicc_parser.add_argument('--min-duration-secs', type=int, default=60, + help='Proposed minimum duration of suspension') + suspend_uicc_parser.add_argument('--max-duration-secs', type=int, default=24*60*60, + help='Proposed maximum duration of suspension') + + # not ISO7816-4 but TS 102 221 + @cmd2.with_argparser(suspend_uicc_parser) + def do_suspend_uicc(self, opts): + """Perform the SUSPEND UICC command. Only supported on some UICC.""" + (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs, + max_len_secs=opts.max_duration_secs) + self._cmd.poutput('Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw)) + option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell', formatter_class=argparse.ArgumentDefaultsHelpFormatter) diff --git a/pySim/commands.py b/pySim/commands.py index d53cb3e9..7d09fa93 100644 --- a/pySim/commands.py +++ b/pySim/commands.py @@ -23,7 +23,7 @@ from construct import * from pySim.construct import LV -from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len +from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i from pySim.exceptions import SwMatchError class SimCardCommands(object): @@ -448,3 +448,39 @@ class SimCardCommands(object): data_length = len(payload) // 2 data, sw = self._tp.send_apdu(('80100000%02x' % data_length) + payload) return (data, sw) + + # ETSI TS 102 221 11.1.22 + def suspend_uicc(self, min_len_secs:int=60, max_len_secs:int=43200): + """Send SUSPEND UICC to the card.""" + def encode_duration(secs:int) -> Hexstr: + if secs >= 10*24*60*60: + return '04%02x' % (secs // (10*24*60*60)) + elif secs >= 24*60*60: + return '03%02x' % (secs // (24*60*60)) + elif secs >= 60*60: + return '02%02x' % (secs // (60*60)) + elif secs >= 60: + return '01%02x' % (secs // 60) + else: + return '00%02x' % secs + def decode_duration(enc:Hexstr) -> int: + time_unit = enc[:2] + length = h2i(enc[2:4]) + if time_unit == '04': + return length * 10*24*60*60 + elif time_unit == '03': + return length * 24*60*60 + elif time_unit == '02': + return length * 60*60 + elif time_unit == '01': + return length * 60 + elif time_unit == '00': + return length + else: + raise ValueError('Time unit must be 0x00..0x04') + min_dur_enc = encode_duration(min_len_secs) + max_dur_enc = encode_duration(max_len_secs) + data, sw = self._tp.send_apdu_checksw('8076000004' + min_dur_enc + max_dur_enc) + negotiated_duration_secs = decode_duration(data[:4]) + resume_token = data[4:] + return (negotiated_duration_secs, resume_token, sw)