mirror of https://gerrit.osmocom.org/pysim
filesystem: Introduce the basic notion of 'logical channels'
cards can have multiple logical channels; each logical channel has its own state of what is the current selected file + application. Let's split the RuntimeState class into the global RuntimeState and the per-lchan-specific RuntimeLchan class. This code doesn't actually introduce any code that uses lchans other than the basic logical channel (0), but just modifies the data model to accomodate those in the future. Change-Id: I7aa994b625467d4e46a2edd8123240b930305360
This commit is contained in:
parent
de4c14c0dc
commit
a6c0f880da
|
@ -136,7 +136,8 @@ class PysimApp(cmd2.Cmd):
|
||||||
self.default_category = 'pySim-shell built-in commands'
|
self.default_category = 'pySim-shell built-in commands'
|
||||||
self.card = None
|
self.card = None
|
||||||
self.rs = 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.sl = sl
|
||||||
self.ch = ch
|
self.ch = ch
|
||||||
|
|
||||||
|
@ -165,7 +166,8 @@ class PysimApp(cmd2.Cmd):
|
||||||
|
|
||||||
# Unequip everything from pySim-shell that would not work in unequipped state
|
# Unequip everything from pySim-shell that would not work in unequipped state
|
||||||
if self.rs:
|
if self.rs:
|
||||||
self.rs.unregister_cmds(self)
|
lchan = self.rs.lchan[0]
|
||||||
|
lchan.unregister_cmds(self)
|
||||||
for cmds in [Iso7816Commands, PySimCommands]:
|
for cmds in [Iso7816Commands, PySimCommands]:
|
||||||
cmd_set = self.find_commandsets(cmds)
|
cmd_set = self.find_commandsets(cmds)
|
||||||
if cmd_set:
|
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
|
# When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
|
||||||
# needed to operate on cards.
|
# needed to operate on cards.
|
||||||
if self.card and self.rs:
|
if self.card and self.rs:
|
||||||
|
self.lchan = self.rs.lchan[0]
|
||||||
self._onchange_conserve_write(
|
self._onchange_conserve_write(
|
||||||
'conserve_write', False, self.conserve_write)
|
'conserve_write', False, self.conserve_write)
|
||||||
self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
|
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(Ts102222Commands())
|
||||||
self.register_command_set(PySimCommands())
|
self.register_command_set(PySimCommands())
|
||||||
self.iccid, sw = self.card.read_iccid()
|
self.iccid, sw = self.card.read_iccid()
|
||||||
rs.select('MF', self)
|
self.lchan.select('MF', self)
|
||||||
rc = True
|
rc = True
|
||||||
else:
|
else:
|
||||||
self.poutput("pySim-shell not equipped!")
|
self.poutput("pySim-shell not equipped!")
|
||||||
|
@ -223,8 +226,8 @@ class PysimApp(cmd2.Cmd):
|
||||||
self.cmd2.poutput("<- %s: %s" % (sw, resp))
|
self.cmd2.poutput("<- %s: %s" % (sw, resp))
|
||||||
|
|
||||||
def update_prompt(self):
|
def update_prompt(self):
|
||||||
if self.rs:
|
if self.lchan:
|
||||||
path_list = self.rs.selected_file.fully_qualified_path(
|
path_list = self.lchan.selected_file.fully_qualified_path(
|
||||||
not self.numeric_path)
|
not self.numeric_path)
|
||||||
self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
|
self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
|
||||||
else:
|
else:
|
||||||
|
@ -477,12 +480,12 @@ class PySimCommands(CommandSet):
|
||||||
else:
|
else:
|
||||||
flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
|
flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
|
||||||
selectables = list(
|
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(
|
directory_str = tabulate_str_list(
|
||||||
selectables, width=79, hspace=2, lspace=1, align_left=True)
|
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))
|
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('/'.join(path_list))
|
||||||
self._cmd.poutput(directory_str)
|
self._cmd.poutput(directory_str)
|
||||||
self._cmd.poutput("%d files" % len(selectables))
|
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):
|
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"""
|
"""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:
|
if action_df:
|
||||||
action_df(context, opts)
|
action_df(context, opts)
|
||||||
|
|
||||||
files = self._cmd.rs.selected_file.get_selectables(
|
files = self._cmd.lchan.selected_file.get_selectables(
|
||||||
flags=['FNAMES', 'ANAMES'])
|
flags=['FNAMES', 'ANAMES'])
|
||||||
for f in files:
|
for f in files:
|
||||||
# special case: When no action is performed, just output a directory
|
# special case: When no action is performed, just output a directory
|
||||||
|
@ -511,10 +514,10 @@ class PySimCommands(CommandSet):
|
||||||
if isinstance(files[f], CardDF):
|
if isinstance(files[f], CardDF):
|
||||||
skip_df = False
|
skip_df = False
|
||||||
try:
|
try:
|
||||||
fcp_dec = self._cmd.rs.select(f, self._cmd)
|
fcp_dec = self._cmd.lchan.select(f, self._cmd)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
skip_df = True
|
skip_df = True
|
||||||
df = self._cmd.rs.selected_file
|
df = self._cmd.lchan.selected_file
|
||||||
df_path_list = df.fully_qualified_path(True)
|
df_path_list = df.fully_qualified_path(True)
|
||||||
df_skip_reason_str = '/'.join(df_path_list) + \
|
df_skip_reason_str = '/'.join(df_path_list) + \
|
||||||
"/" + str(f) + ", " + str(e)
|
"/" + str(f) + ", " + str(e)
|
||||||
|
@ -526,17 +529,17 @@ class PySimCommands(CommandSet):
|
||||||
# below, so we must not move up.
|
# below, so we must not move up.
|
||||||
if skip_df == False:
|
if skip_df == False:
|
||||||
self.walk(indent + 1, action_ef, action_df, context, **kwargs)
|
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:
|
elif action_ef:
|
||||||
df_before_action = self._cmd.rs.selected_file
|
df_before_action = self._cmd.lchan.selected_file
|
||||||
action_ef(f, context, **kwargs)
|
action_ef(f, context, **kwargs)
|
||||||
# When walking through the file system tree the action must not
|
# When walking through the file system tree the action must not
|
||||||
# always restore the currently selected file to the file that
|
# always restore the currently selected file to the file that
|
||||||
# was selected before executing the action() callback.
|
# 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"
|
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):
|
def do_tree(self, opts):
|
||||||
"""Display a filesystem-tree with all selectable files"""
|
"""Display a filesystem-tree with all selectable files"""
|
||||||
|
@ -545,7 +548,7 @@ class PySimCommands(CommandSet):
|
||||||
def export_ef(self, filename, context, as_json):
|
def export_ef(self, filename, context, as_json):
|
||||||
""" Select and export a single elementary file (EF) """
|
""" Select and export a single elementary file (EF) """
|
||||||
context['COUNT'] += 1
|
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)
|
# 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
|
# 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)" %
|
self._cmd.poutput("# directory: %s (%s)" %
|
||||||
('/'.join(df_path_list), '/'.join(df_path_list_fid)))
|
('/'.join(df_path_list), '/'.join(df_path_list_fid)))
|
||||||
try:
|
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.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("# structure: %s" % str(structure))
|
||||||
self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.rs.selected_file_fcp_hex))
|
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.rs.selected_file_fcp))
|
self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp))
|
||||||
|
|
||||||
for f in df_path_list:
|
for f in df_path_list:
|
||||||
self._cmd.poutput("select " + str(f))
|
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 structure == 'transparent':
|
||||||
if as_json:
|
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))
|
self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
|
||||||
else:
|
else:
|
||||||
result = self._cmd.rs.read_binary()
|
result = self._cmd.lchan.read_binary()
|
||||||
self._cmd.poutput("update_binary " + str(result[0]))
|
self._cmd.poutput("update_binary " + str(result[0]))
|
||||||
elif structure == 'cyclic' or structure == 'linear_fixed':
|
elif structure == 'cyclic' or structure == 'linear_fixed':
|
||||||
# Use number of records specified in select response
|
# 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:
|
if num_of_rec:
|
||||||
for r in range(1, num_of_rec + 1):
|
for r in range(1, num_of_rec + 1):
|
||||||
if as_json:
|
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)))
|
self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
|
||||||
else:
|
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])))
|
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
|
# 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:
|
while True:
|
||||||
try:
|
try:
|
||||||
if as_json:
|
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)))
|
self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
|
||||||
else:
|
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])))
|
self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
|
||||||
except SwMatchError as e:
|
except SwMatchError as e:
|
||||||
# We are past the last valid record - stop
|
# We are past the last valid record - stop
|
||||||
|
@ -617,9 +620,9 @@ class PySimCommands(CommandSet):
|
||||||
raise e
|
raise e
|
||||||
r = r + 1
|
r = r + 1
|
||||||
elif structure == 'ber_tlv':
|
elif structure == 'ber_tlv':
|
||||||
tags = self._cmd.rs.retrieve_tags()
|
tags = self._cmd.lchan.retrieve_tags()
|
||||||
for t in 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]))
|
(tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
|
||||||
self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
|
self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
|
||||||
else:
|
else:
|
||||||
|
@ -635,8 +638,8 @@ class PySimCommands(CommandSet):
|
||||||
# When reading the file is done, make sure the parent file is
|
# When reading the file is done, make sure the parent file is
|
||||||
# selected again. This will be the usual case, however we need
|
# selected again. This will be the usual case, however we need
|
||||||
# to check before since we must not select the same DF twice
|
# to check before since we must not select the same DF twice
|
||||||
if df != self._cmd.rs.selected_file:
|
if df != self._cmd.lchan.selected_file:
|
||||||
self._cmd.rs.select(df.fid or df.aid, self._cmd)
|
self._cmd.lchan.select(df.fid or df.aid, self._cmd)
|
||||||
|
|
||||||
self._cmd.poutput("#")
|
self._cmd.poutput("#")
|
||||||
|
|
||||||
|
@ -688,13 +691,13 @@ class PySimCommands(CommandSet):
|
||||||
|
|
||||||
def do_reset(self, opts):
|
def do_reset(self, opts):
|
||||||
"""Reset the Card."""
|
"""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.poutput('Card ATR: %s' % atr)
|
||||||
self._cmd.update_prompt()
|
self._cmd.update_prompt()
|
||||||
|
|
||||||
def do_desc(self, opts):
|
def do_desc(self, opts):
|
||||||
"""Display human readable file description for the currently selected file"""
|
"""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:
|
if desc:
|
||||||
self._cmd.poutput(desc)
|
self._cmd.poutput(desc)
|
||||||
else:
|
else:
|
||||||
|
@ -731,21 +734,21 @@ class Iso7816Commands(CommandSet):
|
||||||
def do_select(self, opts):
|
def do_select(self, opts):
|
||||||
"""SELECT a File (ADF/DF/EF)"""
|
"""SELECT a File (ADF/DF/EF)"""
|
||||||
if len(opts.arg_list) == 0:
|
if len(opts.arg_list) == 0:
|
||||||
path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
|
path_list = self._cmd.lchan.selected_file.fully_qualified_path(True)
|
||||||
path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
|
path_list_fid = self._cmd.lchan.selected_file.fully_qualified_path(
|
||||||
False)
|
False)
|
||||||
self._cmd.poutput("currently selected file: " +
|
self._cmd.poutput("currently selected file: " +
|
||||||
'/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
|
'/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
|
||||||
return
|
return
|
||||||
|
|
||||||
path = opts.arg_list[0]
|
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.update_prompt()
|
||||||
self._cmd.poutput_json(fcp_dec)
|
self._cmd.poutput_json(fcp_dec)
|
||||||
|
|
||||||
def complete_select(self, text, line, begidx, endidx) -> List[str]:
|
def complete_select(self, text, line, begidx, endidx) -> List[str]:
|
||||||
"""Command Line tab completion for SELECT"""
|
"""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)
|
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||||
|
|
||||||
def get_code(self, code):
|
def get_code(self, code):
|
||||||
|
@ -851,11 +854,11 @@ class Iso7816Commands(CommandSet):
|
||||||
def do_activate_file(self, opts):
|
def do_activate_file(self, opts):
|
||||||
"""Activate the specified EF. This used to be called REHABILITATE in TS 11.11 for classic
|
"""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."""
|
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]:
|
def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
|
||||||
"""Command Line tab completion for ACTIVATE FILE"""
|
"""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)
|
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||||
|
|
||||||
open_chan_parser = argparse.ArgumentParser()
|
open_chan_parser = argparse.ArgumentParser()
|
||||||
|
@ -880,7 +883,7 @@ class Iso7816Commands(CommandSet):
|
||||||
|
|
||||||
def do_status(self, opts):
|
def do_status(self, opts):
|
||||||
"""Perform the STATUS command."""
|
"""Perform the STATUS command."""
|
||||||
fcp_dec = self._cmd.rs.status()
|
fcp_dec = self._cmd.lchan.status()
|
||||||
self._cmd.poutput_json(fcp_dec)
|
self._cmd.poutput_json(fcp_dec)
|
||||||
|
|
||||||
suspend_uicc_parser = argparse.ArgumentParser()
|
suspend_uicc_parser = argparse.ArgumentParser()
|
||||||
|
|
|
@ -49,6 +49,18 @@ from pySim.commands import SimCardCommands
|
||||||
# tuple: logical-and of the listed services requires this file
|
# tuple: logical-and of the listed services requires this file
|
||||||
CardFileService = Union[int, List[int], Tuple[int, ...]]
|
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:
|
class CardFile:
|
||||||
"""Base class for all objects in the smart card filesystem.
|
"""Base class for all objects in the smart card filesystem.
|
||||||
Serve as a common ancestor to all other file types; rarely used directly.
|
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)
|
@cmd2.with_argparser(dec_hex_parser)
|
||||||
def do_decode_hex(self, opts):
|
def do_decode_hex(self, opts):
|
||||||
"""Decode command-line provided hex-string as if it was read from the file."""
|
"""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)
|
self._cmd.poutput_json(data, opts.oneline)
|
||||||
|
|
||||||
read_bin_parser = argparse.ArgumentParser()
|
read_bin_parser = argparse.ArgumentParser()
|
||||||
|
@ -557,7 +569,7 @@ class TransparentEF(CardEF):
|
||||||
@cmd2.with_argparser(read_bin_parser)
|
@cmd2.with_argparser(read_bin_parser)
|
||||||
def do_read_binary(self, opts):
|
def do_read_binary(self, opts):
|
||||||
"""Read binary data from a transparent EF"""
|
"""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)
|
self._cmd.poutput(data)
|
||||||
|
|
||||||
read_bin_dec_parser = argparse.ArgumentParser()
|
read_bin_dec_parser = argparse.ArgumentParser()
|
||||||
|
@ -567,7 +579,7 @@ class TransparentEF(CardEF):
|
||||||
@cmd2.with_argparser(read_bin_dec_parser)
|
@cmd2.with_argparser(read_bin_dec_parser)
|
||||||
def do_read_binary_decoded(self, opts):
|
def do_read_binary_decoded(self, opts):
|
||||||
"""Read + decode data from a transparent EF"""
|
"""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)
|
self._cmd.poutput_json(data, opts.oneline)
|
||||||
|
|
||||||
upd_bin_parser = argparse.ArgumentParser()
|
upd_bin_parser = argparse.ArgumentParser()
|
||||||
|
@ -579,7 +591,7 @@ class TransparentEF(CardEF):
|
||||||
@cmd2.with_argparser(upd_bin_parser)
|
@cmd2.with_argparser(upd_bin_parser)
|
||||||
def do_update_binary(self, opts):
|
def do_update_binary(self, opts):
|
||||||
"""Update (Write) data of a transparent EF"""
|
"""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:
|
if data:
|
||||||
self._cmd.poutput(data)
|
self._cmd.poutput(data)
|
||||||
|
|
||||||
|
@ -593,18 +605,18 @@ class TransparentEF(CardEF):
|
||||||
def do_update_binary_decoded(self, opts):
|
def do_update_binary_decoded(self, opts):
|
||||||
"""Encode + Update (Write) data of a transparent EF"""
|
"""Encode + Update (Write) data of a transparent EF"""
|
||||||
if opts.json_path:
|
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,
|
js_path_modify(data_json, opts.json_path,
|
||||||
json.loads(opts.data))
|
json.loads(opts.data))
|
||||||
else:
|
else:
|
||||||
data_json = json.loads(opts.data)
|
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:
|
if data:
|
||||||
self._cmd.poutput_json(data)
|
self._cmd.poutput_json(data)
|
||||||
|
|
||||||
def do_edit_binary_decoded(self, opts):
|
def do_edit_binary_decoded(self, opts):
|
||||||
"""Edit the JSON representation of the EF contents in an editor."""
|
"""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:
|
with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
|
||||||
filename = '%s/file' % dirname
|
filename = '%s/file' % dirname
|
||||||
# write existing data as JSON to file
|
# write existing data as JSON to file
|
||||||
|
@ -617,7 +629,7 @@ class TransparentEF(CardEF):
|
||||||
if edited_json == orig_json:
|
if edited_json == orig_json:
|
||||||
self._cmd.poutput("Data not modified, skipping write")
|
self._cmd.poutput("Data not modified, skipping write")
|
||||||
else:
|
else:
|
||||||
(data, sw) = self._cmd.rs.update_binary_dec(edited_json)
|
(data, sw) = self._cmd.lchan.update_binary_dec(edited_json)
|
||||||
if data:
|
if data:
|
||||||
self._cmd.poutput_json(data)
|
self._cmd.poutput_json(data)
|
||||||
|
|
||||||
|
@ -768,7 +780,7 @@ class LinFixedEF(CardEF):
|
||||||
@cmd2.with_argparser(dec_hex_parser)
|
@cmd2.with_argparser(dec_hex_parser)
|
||||||
def do_decode_hex(self, opts):
|
def do_decode_hex(self, opts):
|
||||||
"""Decode command-line provided hex-string as if it was read from the file."""
|
"""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)
|
self._cmd.poutput_json(data, opts.oneline)
|
||||||
|
|
||||||
read_rec_parser = argparse.ArgumentParser()
|
read_rec_parser = argparse.ArgumentParser()
|
||||||
|
@ -782,7 +794,7 @@ class LinFixedEF(CardEF):
|
||||||
"""Read one or multiple records from a record-oriented EF"""
|
"""Read one or multiple records from a record-oriented EF"""
|
||||||
for r in range(opts.count):
|
for r in range(opts.count):
|
||||||
recnr = opts.record_nr + r
|
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):
|
if (len(data) > 0):
|
||||||
recstr = str(data)
|
recstr = str(data)
|
||||||
else:
|
else:
|
||||||
|
@ -798,7 +810,7 @@ class LinFixedEF(CardEF):
|
||||||
@cmd2.with_argparser(read_rec_dec_parser)
|
@cmd2.with_argparser(read_rec_dec_parser)
|
||||||
def do_read_record_decoded(self, opts):
|
def do_read_record_decoded(self, opts):
|
||||||
"""Read + decode a record from a record-oriented EF"""
|
"""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)
|
self._cmd.poutput_json(data, opts.oneline)
|
||||||
|
|
||||||
read_recs_parser = argparse.ArgumentParser()
|
read_recs_parser = argparse.ArgumentParser()
|
||||||
|
@ -806,9 +818,9 @@ class LinFixedEF(CardEF):
|
||||||
@cmd2.with_argparser(read_recs_parser)
|
@cmd2.with_argparser(read_recs_parser)
|
||||||
def do_read_records(self, opts):
|
def do_read_records(self, opts):
|
||||||
"""Read all records from a record-oriented EF"""
|
"""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):
|
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):
|
if (len(data) > 0):
|
||||||
recstr = str(data)
|
recstr = str(data)
|
||||||
else:
|
else:
|
||||||
|
@ -822,11 +834,11 @@ class LinFixedEF(CardEF):
|
||||||
@cmd2.with_argparser(read_recs_dec_parser)
|
@cmd2.with_argparser(read_recs_dec_parser)
|
||||||
def do_read_records_decoded(self, opts):
|
def do_read_records_decoded(self, opts):
|
||||||
"""Read + decode all records from a record-oriented EF"""
|
"""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
|
# collect all results in list so they are rendered as JSON list when printing
|
||||||
data_list = []
|
data_list = []
|
||||||
for recnr in range(1, 1 + num_of_rec):
|
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)
|
data_list.append(data)
|
||||||
self._cmd.poutput_json(data_list, opts.oneline)
|
self._cmd.poutput_json(data_list, opts.oneline)
|
||||||
|
|
||||||
|
@ -839,7 +851,7 @@ class LinFixedEF(CardEF):
|
||||||
@cmd2.with_argparser(upd_rec_parser)
|
@cmd2.with_argparser(upd_rec_parser)
|
||||||
def do_update_record(self, opts):
|
def do_update_record(self, opts):
|
||||||
"""Update (write) data to a record-oriented EF"""
|
"""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:
|
if data:
|
||||||
self._cmd.poutput(data)
|
self._cmd.poutput(data)
|
||||||
|
|
||||||
|
@ -855,12 +867,12 @@ class LinFixedEF(CardEF):
|
||||||
def do_update_record_decoded(self, opts):
|
def do_update_record_decoded(self, opts):
|
||||||
"""Encode + Update (write) data to a record-oriented EF"""
|
"""Encode + Update (write) data to a record-oriented EF"""
|
||||||
if opts.json_path:
|
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,
|
js_path_modify(data_json, opts.json_path,
|
||||||
json.loads(opts.data))
|
json.loads(opts.data))
|
||||||
else:
|
else:
|
||||||
data_json = json.loads(opts.data)
|
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)
|
opts.record_nr, data_json)
|
||||||
if data:
|
if data:
|
||||||
self._cmd.poutput(data)
|
self._cmd.poutput(data)
|
||||||
|
@ -872,7 +884,7 @@ class LinFixedEF(CardEF):
|
||||||
@cmd2.with_argparser(edit_rec_dec_parser)
|
@cmd2.with_argparser(edit_rec_dec_parser)
|
||||||
def do_edit_record_decoded(self, opts):
|
def do_edit_record_decoded(self, opts):
|
||||||
"""Edit the JSON representation of one record in an editor."""
|
"""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:
|
with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
|
||||||
filename = '%s/file' % dirname
|
filename = '%s/file' % dirname
|
||||||
# write existing data as JSON to file
|
# write existing data as JSON to file
|
||||||
|
@ -885,7 +897,7 @@ class LinFixedEF(CardEF):
|
||||||
if edited_json == orig_json:
|
if edited_json == orig_json:
|
||||||
self._cmd.poutput("Data not modified, skipping write")
|
self._cmd.poutput("Data not modified, skipping write")
|
||||||
else:
|
else:
|
||||||
(data, sw) = self._cmd.rs.update_record_dec(
|
(data, sw) = self._cmd.lchan.update_record_dec(
|
||||||
opts.record_nr, edited_json)
|
opts.record_nr, edited_json)
|
||||||
if data:
|
if data:
|
||||||
self._cmd.poutput_json(data)
|
self._cmd.poutput_json(data)
|
||||||
|
@ -1192,12 +1204,12 @@ class BerTlvEF(CardEF):
|
||||||
@cmd2.with_argparser(retrieve_data_parser)
|
@cmd2.with_argparser(retrieve_data_parser)
|
||||||
def do_retrieve_data(self, opts):
|
def do_retrieve_data(self, opts):
|
||||||
"""Retrieve (Read) data from a BER-TLV EF"""
|
"""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)
|
self._cmd.poutput(data)
|
||||||
|
|
||||||
def do_retrieve_tags(self, opts):
|
def do_retrieve_tags(self, opts):
|
||||||
"""List tags available in a given BER-TLV EF"""
|
"""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)
|
self._cmd.poutput(tags)
|
||||||
|
|
||||||
set_data_parser = argparse.ArgumentParser()
|
set_data_parser = argparse.ArgumentParser()
|
||||||
|
@ -1209,7 +1221,7 @@ class BerTlvEF(CardEF):
|
||||||
@cmd2.with_argparser(set_data_parser)
|
@cmd2.with_argparser(set_data_parser)
|
||||||
def do_set_data(self, opts):
|
def do_set_data(self, opts):
|
||||||
"""Set (Write) data for a given tag in a BER-TLV EF"""
|
"""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:
|
if data:
|
||||||
self._cmd.poutput(data)
|
self._cmd.poutput(data)
|
||||||
|
|
||||||
|
@ -1220,7 +1232,7 @@ class BerTlvEF(CardEF):
|
||||||
@cmd2.with_argparser(del_data_parser)
|
@cmd2.with_argparser(del_data_parser)
|
||||||
def do_delete_data(self, opts):
|
def do_delete_data(self, opts):
|
||||||
"""Delete data for a given tag in a BER-TLV EF"""
|
"""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:
|
if data:
|
||||||
self._cmd.poutput(data)
|
self._cmd.poutput(data)
|
||||||
|
|
||||||
|
@ -1252,11 +1264,10 @@ class RuntimeState:
|
||||||
"""
|
"""
|
||||||
self.mf = CardMF(profile=profile)
|
self.mf = CardMF(profile=profile)
|
||||||
self.card = card
|
self.card = card
|
||||||
self.selected_file = self.mf # type: CardDF
|
|
||||||
self.selected_adf = None
|
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.selected_file_fcp = None
|
self.lchan = {}
|
||||||
self.selected_file_fcp_hex = None
|
# the basic logical channel always exists
|
||||||
|
self.lchan[0] = RuntimeLchan(0, self)
|
||||||
|
|
||||||
# make sure the class and selection control bytes, which are specified
|
# make sure the class and selection control bytes, which are specified
|
||||||
# by the card profile are used
|
# by the card profile are used
|
||||||
|
@ -1315,6 +1326,66 @@ class RuntimeState:
|
||||||
pass
|
pass
|
||||||
return apps_taken
|
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:
|
def selected_file_descriptor_byte(self) -> dict:
|
||||||
return self.selected_file_fcp['file_descriptor']['file_descriptor_byte']
|
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]:
|
def selected_file_num_of_rec(self) -> Optional[int]:
|
||||||
return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
|
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:
|
def get_cwd(self) -> CardDF:
|
||||||
"""Obtain the current working directory.
|
"""Obtain the current working directory.
|
||||||
|
|
||||||
|
@ -1384,7 +1444,7 @@ class RuntimeState:
|
||||||
# card profile.
|
# card profile.
|
||||||
if app and hasattr(app, "interpret_sw"):
|
if app and hasattr(app, "interpret_sw"):
|
||||||
res = app.interpret_sw(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):
|
def probe_file(self, fid: str, cmd_app=None):
|
||||||
"""Blindly try to select a file and automatically add a matching file
|
"""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)
|
"Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(data, sw) = self.card._scc.select_file(fid)
|
(data, sw) = self.rs.card._scc.select_file(fid)
|
||||||
except SwMatchError as swm:
|
except SwMatchError as swm:
|
||||||
k = self.interpret_sw(swm.sw_actual)
|
k = self.interpret_sw(swm.sw_actual)
|
||||||
if not k:
|
if not k:
|
||||||
|
@ -1446,10 +1506,10 @@ class RuntimeState:
|
||||||
for p in inter_path:
|
for p in inter_path:
|
||||||
try:
|
try:
|
||||||
if isinstance(p, CardADF):
|
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
|
self.selected_adf = p
|
||||||
else:
|
else:
|
||||||
(data, sw) = self.card._scc.select_file(p.fid)
|
(data, sw) = self.rs.card._scc.select_file(p.fid)
|
||||||
self.selected_file = p
|
self.selected_file = p
|
||||||
except SwMatchError as swm:
|
except SwMatchError as swm:
|
||||||
self._select_post(cmd_app)
|
self._select_post(cmd_app)
|
||||||
|
@ -1490,9 +1550,9 @@ class RuntimeState:
|
||||||
f = sels[name]
|
f = sels[name]
|
||||||
try:
|
try:
|
||||||
if isinstance(f, CardADF):
|
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:
|
else:
|
||||||
(data, sw) = self.card._scc.select_file(f.fid)
|
(data, sw) = self.rs.card._scc.select_file(f.fid)
|
||||||
self.selected_file = f
|
self.selected_file = f
|
||||||
except SwMatchError as swm:
|
except SwMatchError as swm:
|
||||||
k = self.interpret_sw(swm.sw_actual)
|
k = self.interpret_sw(swm.sw_actual)
|
||||||
|
@ -1512,7 +1572,7 @@ class RuntimeState:
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
"""Request STATUS (current selected file FCP) from card."""
|
"""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)
|
return self.selected_file.decode_select_response(data)
|
||||||
|
|
||||||
def get_file_for_selectable(self, name: str):
|
def get_file_for_selectable(self, name: str):
|
||||||
|
@ -1523,7 +1583,7 @@ class RuntimeState:
|
||||||
"""Request ACTIVATE FILE of specified file."""
|
"""Request ACTIVATE FILE of specified file."""
|
||||||
sels = self.selected_file.get_selectables()
|
sels = self.selected_file.get_selectables()
|
||||||
f = sels[name]
|
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
|
return data, sw
|
||||||
|
|
||||||
def read_binary(self, length: int = None, offset: int = 0):
|
def read_binary(self, length: int = None, offset: int = 0):
|
||||||
|
@ -1537,7 +1597,7 @@ class RuntimeState:
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.selected_file, TransparentEF):
|
if not isinstance(self.selected_file, TransparentEF):
|
||||||
raise TypeError("Only works with 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]:
|
def read_binary_dec(self) -> Tuple[dict, str]:
|
||||||
"""Read [part of] a transparent EF binary data and decode it.
|
"""Read [part of] a transparent EF binary data and decode it.
|
||||||
|
@ -1561,7 +1621,7 @@ class RuntimeState:
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.selected_file, TransparentEF):
|
if not isinstance(self.selected_file, TransparentEF):
|
||||||
raise TypeError("Only works with 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):
|
def update_binary_dec(self, data: dict):
|
||||||
"""Update transparent EF from abstract data. Encodes the data to binary and
|
"""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):
|
if not isinstance(self.selected_file, LinFixedEF):
|
||||||
raise TypeError("Only works with Linear Fixed EF")
|
raise TypeError("Only works with Linear Fixed EF")
|
||||||
# returns a string of hex nibbles
|
# 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]:
|
def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
|
||||||
"""Read a record and decode it to abstract data.
|
"""Read a record and decode it to abstract data.
|
||||||
|
@ -1606,7 +1666,7 @@ class RuntimeState:
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.selected_file, LinFixedEF):
|
if not isinstance(self.selected_file, LinFixedEF):
|
||||||
raise TypeError("Only works with Linear Fixed EF")
|
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):
|
def update_record_dec(self, rec_nr: int, data: dict):
|
||||||
"""Update a record with given abstract data. Will encode abstract to binary data
|
"""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):
|
if not isinstance(self.selected_file, BerTlvEF):
|
||||||
raise TypeError("Only works with BER-TLV EF")
|
raise TypeError("Only works with BER-TLV EF")
|
||||||
# returns a string of hex nibbles
|
# 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):
|
def retrieve_tags(self):
|
||||||
"""Retrieve tags available on BER-TLV EF.
|
"""Retrieve tags available on BER-TLV EF.
|
||||||
|
@ -1640,7 +1700,7 @@ class RuntimeState:
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.selected_file, BerTlvEF):
|
if not isinstance(self.selected_file, BerTlvEF):
|
||||||
raise TypeError("Only works with BER-TLV EF")
|
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))
|
tag, length, value, remainder = bertlv_parse_one(h2b(data))
|
||||||
return list(value)
|
return list(value)
|
||||||
|
|
||||||
|
@ -1653,7 +1713,7 @@ class RuntimeState:
|
||||||
"""
|
"""
|
||||||
if not isinstance(self.selected_file, BerTlvEF):
|
if not isinstance(self.selected_file, BerTlvEF):
|
||||||
raise TypeError("Only works with BER-TLV EF")
|
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):
|
def unregister_cmds(self, cmd_app=None):
|
||||||
"""Unregister all file specific commands."""
|
"""Unregister all file specific commands."""
|
||||||
|
|
|
@ -686,19 +686,19 @@ class EF_ARR(LinFixedEF):
|
||||||
@cmd2.with_argparser(LinFixedEF.ShellCommands.read_rec_dec_parser)
|
@cmd2.with_argparser(LinFixedEF.ShellCommands.read_rec_dec_parser)
|
||||||
def do_read_arr_record(self, opts):
|
def do_read_arr_record(self, opts):
|
||||||
"""Read one EF.ARR record in flattened, human-friendly form."""
|
"""Read one EF.ARR record in flattened, human-friendly form."""
|
||||||
(data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
|
(data, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
|
||||||
data = self._cmd.rs.selected_file.flatten(data)
|
data = self._cmd.lchan.selected_file.flatten(data)
|
||||||
self._cmd.poutput_json(data, opts.oneline)
|
self._cmd.poutput_json(data, opts.oneline)
|
||||||
|
|
||||||
@cmd2.with_argparser(LinFixedEF.ShellCommands.read_recs_dec_parser)
|
@cmd2.with_argparser(LinFixedEF.ShellCommands.read_recs_dec_parser)
|
||||||
def do_read_arr_records(self, opts):
|
def do_read_arr_records(self, opts):
|
||||||
"""Read + decode all EF.ARR records in flattened, human-friendly form."""
|
"""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
|
# collect all results in list so they are rendered as JSON list when printing
|
||||||
data_list = []
|
data_list = []
|
||||||
for recnr in range(1, 1 + num_of_rec):
|
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 = self._cmd.rs.selected_file.flatten(data)
|
data = self._cmd.lchan.selected_file.flatten(data)
|
||||||
data_list.append(data)
|
data_list.append(data)
|
||||||
self._cmd.poutput_json(data_list, opts.oneline)
|
self._cmd.poutput_json(data_list, opts.oneline)
|
||||||
|
|
||||||
|
|
|
@ -51,12 +51,12 @@ class Ts102222Commands(CommandSet):
|
||||||
if not opts.force_delete:
|
if not opts.force_delete:
|
||||||
self._cmd.perror("Refusing to permanently delete the file, please read the help text.")
|
self._cmd.perror("Refusing to permanently delete the file, please read the help text.")
|
||||||
return
|
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)
|
(data, sw) = self._cmd.card._scc.delete_file(f.fid)
|
||||||
|
|
||||||
def complete_delete_file(self, text, line, begidx, endidx) -> List[str]:
|
def complete_delete_file(self, text, line, begidx, endidx) -> List[str]:
|
||||||
"""Command Line tab completion for DELETE FILE"""
|
"""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)
|
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||||
|
|
||||||
termdf_parser = argparse.ArgumentParser()
|
termdf_parser = argparse.ArgumentParser()
|
||||||
|
@ -72,12 +72,12 @@ class Ts102222Commands(CommandSet):
|
||||||
if not opts.force:
|
if not opts.force:
|
||||||
self._cmd.perror("Refusing to terminate the file, please read the help text.")
|
self._cmd.perror("Refusing to terminate the file, please read the help text.")
|
||||||
return
|
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)
|
(data, sw) = self._cmd.card._scc.terminate_df(f.fid)
|
||||||
|
|
||||||
def complete_terminate_df(self, text, line, begidx, endidx) -> List[str]:
|
def complete_terminate_df(self, text, line, begidx, endidx) -> List[str]:
|
||||||
"""Command Line tab completion for TERMINATE DF"""
|
"""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)
|
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||||
|
|
||||||
@cmd2.with_argparser(termdf_parser)
|
@cmd2.with_argparser(termdf_parser)
|
||||||
|
@ -88,12 +88,12 @@ class Ts102222Commands(CommandSet):
|
||||||
if not opts.force:
|
if not opts.force:
|
||||||
self._cmd.perror("Refusing to terminate the file, please read the help text.")
|
self._cmd.perror("Refusing to terminate the file, please read the help text.")
|
||||||
return
|
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)
|
(data, sw) = self._cmd.card._scc.terminate_ef(f.fid)
|
||||||
|
|
||||||
def complete_terminate_ef(self, text, line, begidx, endidx) -> List[str]:
|
def complete_terminate_ef(self, text, line, begidx, endidx) -> List[str]:
|
||||||
"""Command Line tab completion for TERMINATE EF"""
|
"""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)
|
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
|
||||||
|
|
||||||
tcard_parser = argparse.ArgumentParser()
|
tcard_parser = argparse.ArgumentParser()
|
||||||
|
@ -154,7 +154,7 @@ class Ts102222Commands(CommandSet):
|
||||||
fcp = FcpTemplate(children=ies)
|
fcp = FcpTemplate(children=ies)
|
||||||
(data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv()))
|
(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
|
# 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 = argparse.ArgumentParser()
|
||||||
createdf_parser.add_argument('FILE_ID', type=str, help='File Identifier as 4-character hex string')
|
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)
|
fcp = FcpTemplate(children=ies)
|
||||||
(data, sw) = self._cmd.card._scc.create_file(b2h(fcp.to_tlv()))
|
(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
|
# 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)
|
||||||
|
|
|
@ -588,7 +588,7 @@ class EF_UServiceTable(TransparentEF):
|
||||||
|
|
||||||
def get_active_services(self, cmd):
|
def get_active_services(self, cmd):
|
||||||
# obtain list of currently active services
|
# obtain list of currently active services
|
||||||
(service_data, sw) = cmd.rs.read_binary_dec()
|
(service_data, sw) = cmd.lchan.read_binary_dec()
|
||||||
active_services = []
|
active_services = []
|
||||||
for s in service_data.keys():
|
for s in service_data.keys():
|
||||||
if service_data[s]['activated']:
|
if service_data[s]['activated']:
|
||||||
|
@ -609,7 +609,7 @@ class EF_UServiceTable(TransparentEF):
|
||||||
for f in files_by_service[s]:
|
for f in files_by_service[s]:
|
||||||
should_exist = f.should_exist_for_services(active_services)
|
should_exist = f.should_exist_for_services(active_services)
|
||||||
try:
|
try:
|
||||||
cmd.rs.select_file(f)
|
cmd.lchan.select_file(f)
|
||||||
sw = None
|
sw = None
|
||||||
exists = True
|
exists = True
|
||||||
except SwMatchError as e:
|
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))
|
cmd.perror(" ERROR: File %s is not selectable (%s) but should!" % (f, sw))
|
||||||
finally:
|
finally:
|
||||||
# re-select the EF.UST
|
# re-select the EF.UST
|
||||||
cmd.rs.select_file(self)
|
cmd.lchan.select_file(self)
|
||||||
return num_problems
|
return num_problems
|
||||||
|
|
||||||
class EF_UST(EF_UServiceTable):
|
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
|
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
|
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."""
|
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)
|
num_problems = selected_file.ust_service_check(self._cmd)
|
||||||
# obtain list of currently active services
|
# obtain list of currently active services
|
||||||
active_services = selected_file.get_active_services(self._cmd)
|
active_services = selected_file.get_active_services(self._cmd)
|
||||||
|
|
|
@ -131,7 +131,7 @@ class EF_IST(EF_UServiceTable):
|
||||||
absent/deactivated. This performs a consistency check to ensure that no services are activated
|
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
|
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."""
|
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)
|
num_problems = selected_file.ust_service_check(self._cmd)
|
||||||
self._cmd.poutput("===> %u service / file inconsistencies detected" % num_problems)
|
self._cmd.poutput("===> %u service / file inconsistencies detected" % num_problems)
|
||||||
|
|
||||||
|
|
|
@ -542,15 +542,15 @@ class EF_IMSI(TransparentEF):
|
||||||
"""Change the plmn part of the IMSI"""
|
"""Change the plmn part of the IMSI"""
|
||||||
plmn = arg.strip()
|
plmn = arg.strip()
|
||||||
if len(plmn) == 5 or len(plmn) == 6:
|
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:
|
if sw == '9000' and len(data['imsi'])-len(plmn) == 10:
|
||||||
imsi = data['imsi']
|
imsi = data['imsi']
|
||||||
msin = imsi[len(plmn):]
|
msin = imsi[len(plmn):]
|
||||||
(data, sw) = self._cmd.rs.update_binary_dec(
|
(data, sw) = self._cmd.lchan.update_binary_dec(
|
||||||
{'imsi': plmn+msin})
|
{'imsi': plmn+msin})
|
||||||
if sw == '9000' and data:
|
if sw == '9000' and data:
|
||||||
self._cmd.poutput_json(
|
self._cmd.poutput_json(
|
||||||
self._cmd.rs.selected_file.decode_hex(data))
|
self._cmd.lchan.selected_file.decode_hex(data))
|
||||||
else:
|
else:
|
||||||
raise ValueError("PLMN length does not match IMSI length")
|
raise ValueError("PLMN length does not match IMSI length")
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue