diff --git a/pySim-shell.py b/pySim-shell.py index dd8b8d0a..c05fd7a9 100755 --- a/pySim-shell.py +++ b/pySim-shell.py @@ -136,7 +136,8 @@ class PysimApp(cmd2.Cmd): self.default_category = 'pySim-shell built-in commands' self.card = None self.rs = None - self.py_locals = {'card': self.card, 'rs': self.rs} + self.lchan = None + self.py_locals = {'card': self.card, 'rs': self.rs, 'lchan': self.lchan} self.sl = sl self.ch = ch @@ -165,7 +166,8 @@ class PysimApp(cmd2.Cmd): # Unequip everything from pySim-shell that would not work in unequipped state if self.rs: - self.rs.unregister_cmds(self) + lchan = self.rs.lchan[0] + lchan.unregister_cmds(self) for cmds in [Iso7816Commands, PySimCommands]: cmd_set = self.find_commandsets(cmds) if cmd_set: @@ -177,6 +179,7 @@ class PysimApp(cmd2.Cmd): # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is # needed to operate on cards. if self.card and self.rs: + self.lchan = self.rs.lchan[0] self._onchange_conserve_write( 'conserve_write', False, self.conserve_write) self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace) @@ -184,7 +187,7 @@ class PysimApp(cmd2.Cmd): self.register_command_set(Ts102222Commands()) self.register_command_set(PySimCommands()) self.iccid, sw = self.card.read_iccid() - rs.select('MF', self) + self.lchan.select('MF', self) rc = True else: self.poutput("pySim-shell not equipped!") @@ -223,8 +226,8 @@ class PysimApp(cmd2.Cmd): self.cmd2.poutput("<- %s: %s" % (sw, resp)) def update_prompt(self): - if self.rs: - path_list = self.rs.selected_file.fully_qualified_path( + if self.lchan: + path_list = self.lchan.selected_file.fully_qualified_path( not self.numeric_path) self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list)) else: @@ -477,12 +480,12 @@ class PySimCommands(CommandSet): else: flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES'] selectables = list( - self._cmd.rs.selected_file.get_selectable_names(flags=flags)) + self._cmd.lchan.selected_file.get_selectable_names(flags=flags)) directory_str = tabulate_str_list( selectables, width=79, hspace=2, lspace=1, align_left=True) - path_list = self._cmd.rs.selected_file.fully_qualified_path(True) + path_list = self._cmd.lchan.selected_file.fully_qualified_path(True) self._cmd.poutput('/'.join(path_list)) - path_list = self._cmd.rs.selected_file.fully_qualified_path(False) + path_list = self._cmd.lchan.selected_file.fully_qualified_path(False) self._cmd.poutput('/'.join(path_list)) self._cmd.poutput(directory_str) self._cmd.poutput("%d files" % len(selectables)) @@ -490,11 +493,11 @@ class PySimCommands(CommandSet): def walk(self, indent=0, action_ef=None, action_df=None, context=None, **kwargs): """Recursively walk through the file system, starting at the currently selected DF""" - if isinstance(self._cmd.rs.selected_file, CardDF): + if isinstance(self._cmd.lchan.selected_file, CardDF): if action_df: action_df(context, opts) - files = self._cmd.rs.selected_file.get_selectables( + files = self._cmd.lchan.selected_file.get_selectables( flags=['FNAMES', 'ANAMES']) for f in files: # special case: When no action is performed, just output a directory @@ -511,10 +514,10 @@ class PySimCommands(CommandSet): if isinstance(files[f], CardDF): skip_df = False try: - fcp_dec = self._cmd.rs.select(f, self._cmd) + fcp_dec = self._cmd.lchan.select(f, self._cmd) except Exception as e: skip_df = True - df = self._cmd.rs.selected_file + df = self._cmd.lchan.selected_file df_path_list = df.fully_qualified_path(True) df_skip_reason_str = '/'.join(df_path_list) + \ "/" + str(f) + ", " + str(e) @@ -526,17 +529,17 @@ class PySimCommands(CommandSet): # below, so we must not move up. if skip_df == False: self.walk(indent + 1, action_ef, action_df, context, **kwargs) - fcp_dec = self._cmd.rs.select("..", self._cmd) + fcp_dec = self._cmd.lchan.select("..", self._cmd) elif action_ef: - df_before_action = self._cmd.rs.selected_file + df_before_action = self._cmd.lchan.selected_file action_ef(f, context, **kwargs) # When walking through the file system tree the action must not # always restore the currently selected file to the file that # was selected before executing the action() callback. - if df_before_action != self._cmd.rs.selected_file: + if df_before_action != self._cmd.lchan.selected_file: raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected" - % (str(self._cmd.rs.selected_file), str(df_before_action))) + % (str(self._cmd.lchan.selected_file), str(df_before_action))) def do_tree(self, opts): """Display a filesystem-tree with all selectable files""" @@ -545,7 +548,7 @@ class PySimCommands(CommandSet): def export_ef(self, filename, context, as_json): """ Select and export a single elementary file (EF) """ context['COUNT'] += 1 - df = self._cmd.rs.selected_file + df = self._cmd.lchan.selected_file # The currently selected file (not the file we are going to export) # must always be an ADF or DF. From this starting point we select @@ -564,36 +567,36 @@ class PySimCommands(CommandSet): self._cmd.poutput("# directory: %s (%s)" % ('/'.join(df_path_list), '/'.join(df_path_list_fid))) try: - fcp_dec = self._cmd.rs.select(filename, self._cmd) + fcp_dec = self._cmd.lchan.select(filename, self._cmd) self._cmd.poutput("# file: %s (%s)" % ( - self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid)) + self._cmd.lchan.selected_file.name, self._cmd.lchan.selected_file.fid)) - structure = self._cmd.rs.selected_file_structure() + structure = self._cmd.lchan.selected_file_structure() self._cmd.poutput("# structure: %s" % str(structure)) - self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.rs.selected_file_fcp_hex)) - self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.rs.selected_file_fcp)) + self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp_hex)) + self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp)) for f in df_path_list: self._cmd.poutput("select " + str(f)) - self._cmd.poutput("select " + self._cmd.rs.selected_file.name) + self._cmd.poutput("select " + self._cmd.lchan.selected_file.name) if structure == 'transparent': if as_json: - result = self._cmd.rs.read_binary_dec() + result = self._cmd.lchan.read_binary_dec() self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder)) else: - result = self._cmd.rs.read_binary() + result = self._cmd.lchan.read_binary() self._cmd.poutput("update_binary " + str(result[0])) elif structure == 'cyclic' or structure == 'linear_fixed': # Use number of records specified in select response - num_of_rec = self._cmd.rs.selected_file_num_of_rec() + num_of_rec = self._cmd.lchan.selected_file_num_of_rec() if num_of_rec: for r in range(1, num_of_rec + 1): if as_json: - result = self._cmd.rs.read_record_dec(r) + result = self._cmd.lchan.read_record_dec(r) self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder))) else: - result = self._cmd.rs.read_record(r) + result = self._cmd.lchan.read_record(r) self._cmd.poutput("update_record %d %s" % (r, str(result[0]))) # When the select response does not return the number of records, read until we hit the @@ -603,10 +606,10 @@ class PySimCommands(CommandSet): while True: try: if as_json: - result = self._cmd.rs.read_record_dec(r) + result = self._cmd.lchan.read_record_dec(r) self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder))) else: - result = self._cmd.rs.read_record(r) + result = self._cmd.lchan.read_record(r) self._cmd.poutput("update_record %d %s" % (r, str(result[0]))) except SwMatchError as e: # We are past the last valid record - stop @@ -617,9 +620,9 @@ class PySimCommands(CommandSet): raise e r = r + 1 elif structure == 'ber_tlv': - tags = self._cmd.rs.retrieve_tags() + tags = self._cmd.lchan.retrieve_tags() for t in tags: - result = self._cmd.rs.retrieve_data(t) + result = self._cmd.lchan.retrieve_data(t) (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0])) self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val))) else: @@ -635,8 +638,8 @@ class PySimCommands(CommandSet): # When reading the file is done, make sure the parent file is # selected again. This will be the usual case, however we need # to check before since we must not select the same DF twice - if df != self._cmd.rs.selected_file: - self._cmd.rs.select(df.fid or df.aid, self._cmd) + if df != self._cmd.lchan.selected_file: + self._cmd.lchan.select(df.fid or df.aid, self._cmd) self._cmd.poutput("#") @@ -688,13 +691,13 @@ class PySimCommands(CommandSet): def do_reset(self, opts): """Reset the Card.""" - atr = self._cmd.rs.reset(self._cmd) + atr = self._cmd.lchan.reset(self._cmd) self._cmd.poutput('Card ATR: %s' % atr) self._cmd.update_prompt() def do_desc(self, opts): """Display human readable file description for the currently selected file""" - desc = self._cmd.rs.selected_file.desc + desc = self._cmd.lchan.selected_file.desc if desc: self._cmd.poutput(desc) else: @@ -731,21 +734,21 @@ class Iso7816Commands(CommandSet): def do_select(self, opts): """SELECT a File (ADF/DF/EF)""" if len(opts.arg_list) == 0: - path_list = self._cmd.rs.selected_file.fully_qualified_path(True) - path_list_fid = self._cmd.rs.selected_file.fully_qualified_path( + path_list = self._cmd.lchan.selected_file.fully_qualified_path(True) + path_list_fid = self._cmd.lchan.selected_file.fully_qualified_path( False) self._cmd.poutput("currently selected file: " + '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")") return path = opts.arg_list[0] - fcp_dec = self._cmd.rs.select(path, self._cmd) + fcp_dec = self._cmd.lchan.select(path, self._cmd) self._cmd.update_prompt() self._cmd.poutput_json(fcp_dec) def complete_select(self, text, line, begidx, endidx) -> List[str]: """Command Line tab completion for SELECT""" - index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()} + index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()} return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) def get_code(self, code): @@ -851,11 +854,11 @@ class Iso7816Commands(CommandSet): def do_activate_file(self, opts): """Activate the specified EF. This used to be called REHABILITATE in TS 11.11 for classic SIM. You need to specify the name or FID of the file to activate.""" - (data, sw) = self._cmd.rs.activate_file(opts.NAME) + (data, sw) = self._cmd.lchan.activate_file(opts.NAME) def complete_activate_file(self, text, line, begidx, endidx) -> List[str]: """Command Line tab completion for ACTIVATE FILE""" - index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()} + index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()} return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) open_chan_parser = argparse.ArgumentParser() @@ -880,7 +883,7 @@ class Iso7816Commands(CommandSet): def do_status(self, opts): """Perform the STATUS command.""" - fcp_dec = self._cmd.rs.status() + fcp_dec = self._cmd.lchan.status() self._cmd.poutput_json(fcp_dec) suspend_uicc_parser = argparse.ArgumentParser() diff --git a/pySim/filesystem.py b/pySim/filesystem.py index a3e83d67..5e697bfe 100644 --- a/pySim/filesystem.py +++ b/pySim/filesystem.py @@ -49,6 +49,18 @@ from pySim.commands import SimCardCommands # tuple: logical-and of the listed services requires this file CardFileService = Union[int, List[int], Tuple[int, ...]] +def lchan_nr_from_cla(cla: int) -> int: + """Resolve the logical channel number from the CLA byte.""" + # TS 102 221 10.1.1 Coding of Class Byte + if cla >> 4 in [0x0, 0xA, 0x8]: + # Table 10.3 + return cla & 0x03 + elif cla & 0xD0 in [0x40, 0xC0]: + # Table 10.4a + return 4 + (cla & 0x0F) + else: + raise ValueError('Could not determine logical channel for CLA=%2X' % cla) + class CardFile: """Base class for all objects in the smart card filesystem. Serve as a common ancestor to all other file types; rarely used directly. @@ -545,7 +557,7 @@ class TransparentEF(CardEF): @cmd2.with_argparser(dec_hex_parser) def do_decode_hex(self, opts): """Decode command-line provided hex-string as if it was read from the file.""" - data = self._cmd.rs.selected_file.decode_hex(opts.HEXSTR) + data = self._cmd.lchan.selected_file.decode_hex(opts.HEXSTR) self._cmd.poutput_json(data, opts.oneline) read_bin_parser = argparse.ArgumentParser() @@ -557,7 +569,7 @@ class TransparentEF(CardEF): @cmd2.with_argparser(read_bin_parser) def do_read_binary(self, opts): """Read binary data from a transparent EF""" - (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset) + (data, sw) = self._cmd.lchan.read_binary(opts.length, opts.offset) self._cmd.poutput(data) read_bin_dec_parser = argparse.ArgumentParser() @@ -567,7 +579,7 @@ class TransparentEF(CardEF): @cmd2.with_argparser(read_bin_dec_parser) def do_read_binary_decoded(self, opts): """Read + decode data from a transparent EF""" - (data, sw) = self._cmd.rs.read_binary_dec() + (data, sw) = self._cmd.lchan.read_binary_dec() self._cmd.poutput_json(data, opts.oneline) upd_bin_parser = argparse.ArgumentParser() @@ -579,7 +591,7 @@ class TransparentEF(CardEF): @cmd2.with_argparser(upd_bin_parser) def do_update_binary(self, opts): """Update (Write) data of a transparent EF""" - (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset) + (data, sw) = self._cmd.lchan.update_binary(opts.data, opts.offset) if data: self._cmd.poutput(data) @@ -593,18 +605,18 @@ class TransparentEF(CardEF): def do_update_binary_decoded(self, opts): """Encode + Update (Write) data of a transparent EF""" if opts.json_path: - (data_json, sw) = self._cmd.rs.read_binary_dec() + (data_json, sw) = self._cmd.lchan.read_binary_dec() js_path_modify(data_json, opts.json_path, json.loads(opts.data)) else: data_json = json.loads(opts.data) - (data, sw) = self._cmd.rs.update_binary_dec(data_json) + (data, sw) = self._cmd.lchan.update_binary_dec(data_json) if data: self._cmd.poutput_json(data) def do_edit_binary_decoded(self, opts): """Edit the JSON representation of the EF contents in an editor.""" - (orig_json, sw) = self._cmd.rs.read_binary_dec() + (orig_json, sw) = self._cmd.lchan.read_binary_dec() with tempfile.TemporaryDirectory(prefix='pysim_') as dirname: filename = '%s/file' % dirname # write existing data as JSON to file @@ -617,7 +629,7 @@ class TransparentEF(CardEF): if edited_json == orig_json: self._cmd.poutput("Data not modified, skipping write") else: - (data, sw) = self._cmd.rs.update_binary_dec(edited_json) + (data, sw) = self._cmd.lchan.update_binary_dec(edited_json) if data: self._cmd.poutput_json(data) @@ -768,7 +780,7 @@ class LinFixedEF(CardEF): @cmd2.with_argparser(dec_hex_parser) def do_decode_hex(self, opts): """Decode command-line provided hex-string as if it was read from the file.""" - data = self._cmd.rs.selected_file.decode_record_hex(opts.HEXSTR) + data = self._cmd.lchan.selected_file.decode_record_hex(opts.HEXSTR) self._cmd.poutput_json(data, opts.oneline) read_rec_parser = argparse.ArgumentParser() @@ -782,7 +794,7 @@ class LinFixedEF(CardEF): """Read one or multiple records from a record-oriented EF""" for r in range(opts.count): recnr = opts.record_nr + r - (data, sw) = self._cmd.rs.read_record(recnr) + (data, sw) = self._cmd.lchan.read_record(recnr) if (len(data) > 0): recstr = str(data) else: @@ -798,7 +810,7 @@ class LinFixedEF(CardEF): @cmd2.with_argparser(read_rec_dec_parser) def do_read_record_decoded(self, opts): """Read + decode a record from a record-oriented EF""" - (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr) + (data, sw) = self._cmd.lchan.read_record_dec(opts.record_nr) self._cmd.poutput_json(data, opts.oneline) read_recs_parser = argparse.ArgumentParser() @@ -806,9 +818,9 @@ class LinFixedEF(CardEF): @cmd2.with_argparser(read_recs_parser) def do_read_records(self, opts): """Read all records from a record-oriented EF""" - num_of_rec = self._cmd.rs.selected_file_num_of_rec() + num_of_rec = self._cmd.lchan.selected_file_num_of_rec() for recnr in range(1, 1 + num_of_rec): - (data, sw) = self._cmd.rs.read_record(recnr) + (data, sw) = self._cmd.lchan.read_record(recnr) if (len(data) > 0): recstr = str(data) else: @@ -822,11 +834,11 @@ class LinFixedEF(CardEF): @cmd2.with_argparser(read_recs_dec_parser) def do_read_records_decoded(self, opts): """Read + decode all records from a record-oriented EF""" - num_of_rec = self._cmd.rs.selected_file_num_of_rec() + num_of_rec = self._cmd.lchan.selected_file_num_of_rec() # collect all results in list so they are rendered as JSON list when printing data_list = [] for recnr in range(1, 1 + num_of_rec): - (data, sw) = self._cmd.rs.read_record_dec(recnr) + (data, sw) = self._cmd.lchan.read_record_dec(recnr) data_list.append(data) self._cmd.poutput_json(data_list, opts.oneline) @@ -839,7 +851,7 @@ class LinFixedEF(CardEF): @cmd2.with_argparser(upd_rec_parser) def do_update_record(self, opts): """Update (write) data to a record-oriented EF""" - (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data) + (data, sw) = self._cmd.lchan.update_record(opts.record_nr, opts.data) if data: self._cmd.poutput(data) @@ -855,12 +867,12 @@ class LinFixedEF(CardEF): def do_update_record_decoded(self, opts): """Encode + Update (write) data to a record-oriented EF""" if opts.json_path: - (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr) + (data_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr) js_path_modify(data_json, opts.json_path, json.loads(opts.data)) else: data_json = json.loads(opts.data) - (data, sw) = self._cmd.rs.update_record_dec( + (data, sw) = self._cmd.lchan.update_record_dec( opts.record_nr, data_json) if data: self._cmd.poutput(data) @@ -872,7 +884,7 @@ class LinFixedEF(CardEF): @cmd2.with_argparser(edit_rec_dec_parser) def do_edit_record_decoded(self, opts): """Edit the JSON representation of one record in an editor.""" - (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr) + (orig_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr) with tempfile.TemporaryDirectory(prefix='pysim_') as dirname: filename = '%s/file' % dirname # write existing data as JSON to file @@ -885,7 +897,7 @@ class LinFixedEF(CardEF): if edited_json == orig_json: self._cmd.poutput("Data not modified, skipping write") else: - (data, sw) = self._cmd.rs.update_record_dec( + (data, sw) = self._cmd.lchan.update_record_dec( opts.record_nr, edited_json) if data: self._cmd.poutput_json(data) @@ -1192,12 +1204,12 @@ class BerTlvEF(CardEF): @cmd2.with_argparser(retrieve_data_parser) def do_retrieve_data(self, opts): """Retrieve (Read) data from a BER-TLV EF""" - (data, sw) = self._cmd.rs.retrieve_data(opts.tag) + (data, sw) = self._cmd.lchan.retrieve_data(opts.tag) self._cmd.poutput(data) def do_retrieve_tags(self, opts): """List tags available in a given BER-TLV EF""" - tags = self._cmd.rs.retrieve_tags() + tags = self._cmd.lchan.retrieve_tags() self._cmd.poutput(tags) set_data_parser = argparse.ArgumentParser() @@ -1209,7 +1221,7 @@ class BerTlvEF(CardEF): @cmd2.with_argparser(set_data_parser) def do_set_data(self, opts): """Set (Write) data for a given tag in a BER-TLV EF""" - (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data) + (data, sw) = self._cmd.lchan.set_data(opts.tag, opts.data) if data: self._cmd.poutput(data) @@ -1220,7 +1232,7 @@ class BerTlvEF(CardEF): @cmd2.with_argparser(del_data_parser) def do_delete_data(self, opts): """Delete data for a given tag in a BER-TLV EF""" - (data, sw) = self._cmd.rs.set_data(opts.tag, None) + (data, sw) = self._cmd.lchan.set_data(opts.tag, None) if data: self._cmd.poutput(data) @@ -1252,11 +1264,10 @@ class RuntimeState: """ self.mf = CardMF(profile=profile) self.card = card - self.selected_file = self.mf # type: CardDF - self.selected_adf = None self.profile = profile - self.selected_file_fcp = None - self.selected_file_fcp_hex = None + self.lchan = {} + # the basic logical channel always exists + self.lchan[0] = RuntimeLchan(0, self) # make sure the class and selection control bytes, which are specified # by the card profile are used @@ -1315,6 +1326,66 @@ class RuntimeState: pass return apps_taken + def reset(self, cmd_app=None) -> Hexstr: + """Perform physical card reset and obtain ATR. + Args: + cmd_app : Command Application State (for unregistering old file commands) + """ + # delete all lchan != 0 (basic lchan) + for lchan_nr in self.lchan.keys(): + if lchan_nr == 0: + continue + del self.lchan[lchan_nr] + atr = i2h(self.card.reset()) + # select MF to reset internal state and to verify card really works + self.lchan[0].select('MF', cmd_app) + self.lchan[0].selected_adf = None + return atr + + def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan': + """Add a logical channel to the runtime state. You shouldn't call this + directly but always go through RuntimeLchan.add_lchan().""" + if lchan_nr in self.lchan.keys(): + raise ValueError('Cannot create already-existing lchan %d' % lchan_nr) + self.lchan[lchan_nr] = RuntimeLchan(lchan_nr, self) + return self.lchan[lchan_nr] + + def del_lchan(self, lchan_nr: int): + if lchan_nr in self.lchan.keys(): + del self.lchan[lchan_nr] + return True + else: + return False + + def get_lchan_by_cla(self, cla) -> Optional['RuntimeLchan']: + lchan_nr = lchan_nr_from_cla(cla) + if lchan_nr in self.lchan.keys(): + return self.lchan[lchan_nr] + else: + return None + + +class RuntimeLchan: + """Represent the runtime state of a logical channel with a card.""" + + def __init__(self, lchan_nr: int, rs: RuntimeState): + self.lchan_nr = lchan_nr + self.rs = rs + self.selected_file = self.rs.mf + self.selected_adf = None + self.selected_file_fcp = None + self.selected_file_fcp_hex = None + + def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan': + """Add a new logical channel from the current logical channel. Just affects + internal state, doesn't actually open a channel with the UICC.""" + new_lchan = self.rs.add_lchan(lchan_nr) + # See TS 102 221 Table 8.3 + if self.lchan_nr != 0: + new_lchan.selected_file = self.get_cwd() + new_lchan.selected_adf = self.selected_adf + return new_lchan + def selected_file_descriptor_byte(self) -> dict: return self.selected_file_fcp['file_descriptor']['file_descriptor_byte'] @@ -1330,17 +1401,6 @@ class RuntimeState: def selected_file_num_of_rec(self) -> Optional[int]: return self.selected_file_fcp['file_descriptor'].get('num_of_rec') - def reset(self, cmd_app=None) -> Hexstr: - """Perform physical card reset and obtain ATR. - Args: - cmd_app : Command Application State (for unregistering old file commands) - """ - atr = i2h(self.card.reset()) - # select MF to reset internal state and to verify card really works - self.select('MF', cmd_app) - self.selected_adf = None - return atr - def get_cwd(self) -> CardDF: """Obtain the current working directory. @@ -1384,7 +1444,7 @@ class RuntimeState: # card profile. if app and hasattr(app, "interpret_sw"): res = app.interpret_sw(sw) - return res or self.profile.interpret_sw(sw) + return res or self.rs.profile.interpret_sw(sw) def probe_file(self, fid: str, cmd_app=None): """Blindly try to select a file and automatically add a matching file @@ -1394,7 +1454,7 @@ class RuntimeState: "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid) try: - (data, sw) = self.card._scc.select_file(fid) + (data, sw) = self.rs.card._scc.select_file(fid) except SwMatchError as swm: k = self.interpret_sw(swm.sw_actual) if not k: @@ -1446,10 +1506,10 @@ class RuntimeState: for p in inter_path: try: if isinstance(p, CardADF): - (data, sw) = self.card.select_adf_by_aid(p.aid) + (data, sw) = self.rs.card.select_adf_by_aid(p.aid) self.selected_adf = p else: - (data, sw) = self.card._scc.select_file(p.fid) + (data, sw) = self.rs.card._scc.select_file(p.fid) self.selected_file = p except SwMatchError as swm: self._select_post(cmd_app) @@ -1490,9 +1550,9 @@ class RuntimeState: f = sels[name] try: if isinstance(f, CardADF): - (data, sw) = self.card.select_adf_by_aid(f.aid) + (data, sw) = self.rs.card.select_adf_by_aid(f.aid) else: - (data, sw) = self.card._scc.select_file(f.fid) + (data, sw) = self.rs.card._scc.select_file(f.fid) self.selected_file = f except SwMatchError as swm: k = self.interpret_sw(swm.sw_actual) @@ -1512,7 +1572,7 @@ class RuntimeState: def status(self): """Request STATUS (current selected file FCP) from card.""" - (data, sw) = self.card._scc.status() + (data, sw) = self.rs.card._scc.status() return self.selected_file.decode_select_response(data) def get_file_for_selectable(self, name: str): @@ -1523,7 +1583,7 @@ class RuntimeState: """Request ACTIVATE FILE of specified file.""" sels = self.selected_file.get_selectables() f = sels[name] - data, sw = self.card._scc.activate_file(f.fid) + data, sw = self.rs.card._scc.activate_file(f.fid) return data, sw def read_binary(self, length: int = None, offset: int = 0): @@ -1537,7 +1597,7 @@ class RuntimeState: """ if not isinstance(self.selected_file, TransparentEF): raise TypeError("Only works with TransparentEF") - return self.card._scc.read_binary(self.selected_file.fid, length, offset) + return self.rs.card._scc.read_binary(self.selected_file.fid, length, offset) def read_binary_dec(self) -> Tuple[dict, str]: """Read [part of] a transparent EF binary data and decode it. @@ -1561,7 +1621,7 @@ class RuntimeState: """ if not isinstance(self.selected_file, TransparentEF): raise TypeError("Only works with TransparentEF") - return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write) + return self.rs.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.rs.conserve_write) def update_binary_dec(self, data: dict): """Update transparent EF from abstract data. Encodes the data to binary and @@ -1584,7 +1644,7 @@ class RuntimeState: if not isinstance(self.selected_file, LinFixedEF): raise TypeError("Only works with Linear Fixed EF") # returns a string of hex nibbles - return self.card._scc.read_record(self.selected_file.fid, rec_nr) + return self.rs.card._scc.read_record(self.selected_file.fid, rec_nr) def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]: """Read a record and decode it to abstract data. @@ -1606,7 +1666,7 @@ class RuntimeState: """ if not isinstance(self.selected_file, LinFixedEF): raise TypeError("Only works with Linear Fixed EF") - return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.conserve_write) + return self.rs.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.rs.conserve_write) def update_record_dec(self, rec_nr: int, data: dict): """Update a record with given abstract data. Will encode abstract to binary data @@ -1630,7 +1690,7 @@ class RuntimeState: if not isinstance(self.selected_file, BerTlvEF): raise TypeError("Only works with BER-TLV EF") # returns a string of hex nibbles - return self.card._scc.retrieve_data(self.selected_file.fid, tag) + return self.rs.card._scc.retrieve_data(self.selected_file.fid, tag) def retrieve_tags(self): """Retrieve tags available on BER-TLV EF. @@ -1640,7 +1700,7 @@ class RuntimeState: """ if not isinstance(self.selected_file, BerTlvEF): raise TypeError("Only works with BER-TLV EF") - data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c) + data, sw = self.rs.card._scc.retrieve_data(self.selected_file.fid, 0x5c) tag, length, value, remainder = bertlv_parse_one(h2b(data)) return list(value) @@ -1653,7 +1713,7 @@ class RuntimeState: """ if not isinstance(self.selected_file, BerTlvEF): raise TypeError("Only works with BER-TLV EF") - return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write) + return self.rs.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.rs.conserve_write) def unregister_cmds(self, cmd_app=None): """Unregister all file specific commands.""" diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py index 08e836c0..8578f68c 100644 --- a/pySim/ts_102_221.py +++ b/pySim/ts_102_221.py @@ -686,19 +686,19 @@ class EF_ARR(LinFixedEF): @cmd2.with_argparser(LinFixedEF.ShellCommands.read_rec_dec_parser) def do_read_arr_record(self, opts): """Read one EF.ARR record in flattened, human-friendly form.""" - (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr) - data = self._cmd.rs.selected_file.flatten(data) + (data, sw) = self._cmd.lchan.read_record_dec(opts.record_nr) + data = self._cmd.lchan.selected_file.flatten(data) self._cmd.poutput_json(data, opts.oneline) @cmd2.with_argparser(LinFixedEF.ShellCommands.read_recs_dec_parser) def do_read_arr_records(self, opts): """Read + decode all EF.ARR records in flattened, human-friendly form.""" - num_of_rec = self._cmd.rs.selected_file_num_of_rec() + num_of_rec = self._cmd.lchan.selected_file_num_of_rec() # collect all results in list so they are rendered as JSON list when printing data_list = [] for recnr in range(1, 1 + num_of_rec): - (data, sw) = self._cmd.rs.read_record_dec(recnr) - data = self._cmd.rs.selected_file.flatten(data) + (data, sw) = self._cmd.lchan.read_record_dec(recnr) + data = self._cmd.lchan.selected_file.flatten(data) data_list.append(data) self._cmd.poutput_json(data_list, opts.oneline) diff --git a/pySim/ts_102_222.py b/pySim/ts_102_222.py index 3c57e5b6..4843c77a 100644 --- a/pySim/ts_102_222.py +++ b/pySim/ts_102_222.py @@ -51,12 +51,12 @@ class Ts102222Commands(CommandSet): if not opts.force_delete: self._cmd.perror("Refusing to permanently delete the file, please read the help text.") return - f = self._cmd.rs.get_file_for_selectable(opts.NAME) + f = self._cmd.lchan.get_file_for_selectable(opts.NAME) (data, sw) = self._cmd.card._scc.delete_file(f.fid) def complete_delete_file(self, text, line, begidx, endidx) -> List[str]: """Command Line tab completion for DELETE FILE""" - index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()} + index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()} return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) termdf_parser = argparse.ArgumentParser() @@ -72,12 +72,12 @@ class Ts102222Commands(CommandSet): if not opts.force: self._cmd.perror("Refusing to terminate the file, please read the help text.") return - f = self._cmd.rs.get_file_for_selectable(opts.NAME) + f = self._cmd.lchan.get_file_for_selectable(opts.NAME) (data, sw) = self._cmd.card._scc.terminate_df(f.fid) def complete_terminate_df(self, text, line, begidx, endidx) -> List[str]: """Command Line tab completion for TERMINATE DF""" - index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()} + index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()} return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) @cmd2.with_argparser(termdf_parser) @@ -88,12 +88,12 @@ class Ts102222Commands(CommandSet): if not opts.force: self._cmd.perror("Refusing to terminate the file, please read the help text.") return - f = self._cmd.rs.get_file_for_selectable(opts.NAME) + f = self._cmd.lchan.get_file_for_selectable(opts.NAME) (data, sw) = self._cmd.card._scc.terminate_ef(f.fid) def complete_terminate_ef(self, text, line, begidx, endidx) -> List[str]: """Command Line tab completion for TERMINATE EF""" - index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()} + index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()} return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) tcard_parser = argparse.ArgumentParser() @@ -154,7 +154,7 @@ class Ts102222Commands(CommandSet): fcp = FcpTemplate(children=ies) (data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv())) # the newly-created file is automatically selected but our runtime state knows nothing of it - self._cmd.rs.select_file(self._cmd.rs.selected_file) + self._cmd.lchan.select_file(self._cmd.lchan.selected_file) createdf_parser = argparse.ArgumentParser() createdf_parser.add_argument('FILE_ID', type=str, help='File Identifier as 4-character hex string') @@ -205,4 +205,4 @@ class Ts102222Commands(CommandSet): fcp = FcpTemplate(children=ies) (data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv())) # the newly-created file is automatically selected but our runtime state knows nothing of it - self._cmd.rs.select_file(self._cmd.rs.selected_file) + self._cmd.lchan.select_file(self._cmd.lchan.selected_file) diff --git a/pySim/ts_31_102.py b/pySim/ts_31_102.py index 7b714230..d9ee3a09 100644 --- a/pySim/ts_31_102.py +++ b/pySim/ts_31_102.py @@ -588,7 +588,7 @@ class EF_UServiceTable(TransparentEF): def get_active_services(self, cmd): # obtain list of currently active services - (service_data, sw) = cmd.rs.read_binary_dec() + (service_data, sw) = cmd.lchan.read_binary_dec() active_services = [] for s in service_data.keys(): if service_data[s]['activated']: @@ -609,7 +609,7 @@ class EF_UServiceTable(TransparentEF): for f in files_by_service[s]: should_exist = f.should_exist_for_services(active_services) try: - cmd.rs.select_file(f) + cmd.lchan.select_file(f) sw = None exists = True except SwMatchError as e: @@ -623,7 +623,7 @@ class EF_UServiceTable(TransparentEF): cmd.perror(" ERROR: File %s is not selectable (%s) but should!" % (f, sw)) finally: # re-select the EF.UST - cmd.rs.select_file(self) + cmd.lchan.select_file(self) return num_problems class EF_UST(EF_UServiceTable): @@ -652,7 +652,7 @@ class EF_UST(EF_UServiceTable): absent/deactivated. This performs a consistency check to ensure that no services are activated for files that are not - and vice-versa, no files are activated for services that are not. Error messages are printed for every inconsistency found.""" - selected_file = self._cmd.rs.selected_file + selected_file = self._cmd.lchan.selected_file num_problems = selected_file.ust_service_check(self._cmd) # obtain list of currently active services active_services = selected_file.get_active_services(self._cmd) diff --git a/pySim/ts_31_103.py b/pySim/ts_31_103.py index 83ac6c1a..77eb0f22 100644 --- a/pySim/ts_31_103.py +++ b/pySim/ts_31_103.py @@ -131,7 +131,7 @@ class EF_IST(EF_UServiceTable): absent/deactivated. This performs a consistency check to ensure that no services are activated for files that are not - and vice-versa, no files are activated for services that are not. Error messages are printed for every inconsistency found.""" - selected_file = self._cmd.rs.selected_file + selected_file = self._cmd.lchan.selected_file num_problems = selected_file.ust_service_check(self._cmd) self._cmd.poutput("===> %u service / file inconsistencies detected" % num_problems) diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py index e4cbfd2f..21fd54b3 100644 --- a/pySim/ts_51_011.py +++ b/pySim/ts_51_011.py @@ -542,15 +542,15 @@ class EF_IMSI(TransparentEF): """Change the plmn part of the IMSI""" plmn = arg.strip() if len(plmn) == 5 or len(plmn) == 6: - (data, sw) = self._cmd.rs.read_binary_dec() + (data, sw) = self._cmd.lchan.read_binary_dec() if sw == '9000' and len(data['imsi'])-len(plmn) == 10: imsi = data['imsi'] msin = imsi[len(plmn):] - (data, sw) = self._cmd.rs.update_binary_dec( + (data, sw) = self._cmd.lchan.update_binary_dec( {'imsi': plmn+msin}) if sw == '9000' and data: self._cmd.poutput_json( - self._cmd.rs.selected_file.decode_hex(data)) + self._cmd.lchan.selected_file.decode_hex(data)) else: raise ValueError("PLMN length does not match IMSI length") else: