When sending raw APDUs, we access the scc (SimCardCommands) object via
the scc member in the lchan object. Unfortunately self.lchan will not be
populated when the rs (RuntimeState) object is missing. This is in
particular the case when no profile could be detected for the card,
which is a common situation when we boostrap an unprovisioned card.
So let's access the scc object through the card object. This is also
more logical since when we send raw APDUs we work below the level of
When there is an error on initialization (e.g. card not present), we
should not continue to execute a startup script that was passed with the
pySim-shell commandline. Instead we should print a message that the
startup script was ignored due to errors.
The function init_card catches all exceptions and then returns None
objects for card or rs in case of an error. This does not fit in the
style we pursue in pySim. This is in particular true for library
functions. We want those functions to raise exceptions when something is
wrong, so that we can catch the exception at top level. Let's fix this
for init_card now.
The class property selected_adf is not updated in all locations where an
ADF is selected, this means that we may loose track of the currently
selected ADF in some locations
The point of this is to move generic code out of pySim-shell.py,
paving the way for more/other executables using the full power of
our class model without having to reinvent the wheel.
before this patch:
pySIM-shell (00:MF)> echo foo bar baz
usage: echo [-h] string
echo: error: unrecognized arguments: bar baz
after this patch:
pySIM-shell (00:MF)> echo foo bar baz
foo bar baz
Let's make sure we don't even bother to ask the card to verify
anything as ADM1 pin which is not either a sequence of decimal digits
or an even number of hex digits (even number of bytes).
Let's add a proper argparser instance for the 'verify_adm' command,
avoiding situations where the user types 'verif_adm --help' and then
--help is interpreted as the PIN value, removing one more attempt from
the failed ADM1 counter.
Let's use that opportunity to improve the documentation of the command.
The method build_select_path_to chops off the first element of the
current path. This is done to prevent re-selection of the first file in
the current path.
Unfortunately chopping off the first element in the current path does
not work properly in a situation when the current path points to the MF.
This would chop off the first and last element in the list and the for
loop below would run 0 times.
To fix this, let's keep the first element and chop it off from the
When we initialize the reader, we currently tell only which type of
interface we are using, but we do not print the reader number or the
Let's extend the messages so that the path is printed. To prevent
problems with integration-tests, let's also add an environment variable
that we can use to detect when pySim runs inside a integration-test.
This just adds basic support for the ISD-R application and its
associated STORE DATA command which is used for the ES10x interfaces
between off-card entities and the on-card ISD-R.
The argument parser is set up globally for all LinkBase objects in
__init__.py. Since we tend to have only platform independed code in
__init__.py, we should move the argument parser setup into the
specific LinkBase classes.
In in the module __init__.py we print an init message (which type of
LinkBase class is providing the SimLink). However in __init__.py we tend
to have only platform independed code but the message string can already
be categorized as platform depened. Let's put the init message into the
constructor of the concrete classes of LinkBase.
We used to support only single-byte tags in bertlv_encode_tag,
let's fix that. The easy option is to simply call bertlv_parse_tag,
as that already supported multi-byte tags.
We've already had the 'open_channel' and 'close_channel' commands,
which were sent to (and acknowledged by) the card. However,
those commands didn't affect the pySim-shell state, i.e. all
communication would still happen through the default channel '0'.
With this patch we introduce a 'switch_channel' command, using which
the user can determine which of the (previously opened) logical channels
shall be used by pySim-shell.
Now that pySim-shell is aware of logical channels and issues almost
all of its APDUs on the currently selected channel, we must also make
sure that ADF selection by AID (implemented by the CardBase class)
issues the SELECT on the respective logical channel.
Before this patch, SELECT ADF by AID would always be issued on the
primary logical channel (0), irrespective of the currently active
This new approach will "fork" separate SimCardCommands instances
for each RuntimeLchan. Higher-layer code should now always use the
RuntimeLchan.scc rather than the RuntimeState.card._scc in order to
make sure commands use the correct logical channel.
Historically we always only had one instance of SimCardCommands, but
with this patch we can now have multiple instances, one for each lchan.
The SimCardCommands class is aware of the logical channel it runs on
and will patch the CLA byte accordingly.
This fixes the below error during build of the documentation:
pysim/docs/shell.rst:349: ERROR: "<class 'pySim-shell.PySimCommands'>" has no attribute "apdu_cmd_parser"
We already have the open channel and close_channel commands in
pySim-shell. They are sent to the card and acknowledged, respectively.
We also already do have code that can track multiple different logical
channels (the rs.lchan array). However, this is currently only used by
pySim-trace, and not by pySim-shell. Let's change that.
An eUICC that has no active eSIM profile does not have an ICCID. (The
reason for this is that EF.ICCID is part of the eSIM profile).
Unfortunately pySim-shell insists on reading the ICCID from EF.ICCID on
startup in order to use it as a lookup key for verify_adm later.
To solve the problem, let's add a try/except block around the section
where EF.ICCID is read. In case of failure we set the ICCID to None,
While our base classes (TransparentEF / LinFixedEF) always have the
dsecription as 4th argument after "fid, sfid, name", most of the derived
file-specific classes do not share that same argument order.
As seen in the bug fixed by previous Change-Id I7f32c9fd01094620b68b0e54536ecc6cdbe67903
this can have serious consequences. Let's avoid using unnamed
(positional) arguments for the description text altogether.
We were using positional arguments when instantiating instances
of classes like EF_5GS3GPPLOCI with non-default names/fids/...
However, we got the argument order wrong and were passing the
description string in the position of the file size, which causes
exceptions like the following from pySim-trace:
Traceback (most recent call last):
File "/home/laforge/projects/git/pysim/./pySim-trace.py", line 198, in <module>
File "/home/laforge/projects/git/pysim/./pySim-trace.py", line 125, in main
File "/home/laforge/projects/git/pysim/pySim/apdu/__init__.py", line 259, in process
self.processed = method(self.lchan)
File "/home/laforge/projects/git/pysim/pySim/apdu/ts_102_221.py", line 152, in process_on_lchan
if self.cmd_dict['offset'] != 0 or self.lr < self.file.size:
TypeError: '<' not supported between instances of 'int' and 'str'
Let's use named initializers for any arguments after the usual "fid, sfid, name"
We currently catch any exceptions that may occur when the card reader is
initialized. Then we print the exception string or the exception type
when no string is available. However, a failure during the reader
initialization is usually a severe problem, so a traceback would provde
a lot of helpful information to debug the issue. So lets not catch any
exceptions at this level so that we get the full backtrace.
When an exception occurs while initializing or handling the card we
print a traceback, but we do not print any info that allows us to
identify the device that was involved when the exception occurred. Let's
include the device path or number in the error message before we print
In order to make it easier to print the device information, let's add a
__str__() method to all of our devices. This method shall return the
device number or path.
When the try block in which we also call init_card() fails, there may be
no card object, so we must not pass the card object to PysimApp in the
except block. This is also no problem, PysimApp will run without the
card object until the user executes do_equip for a second attempt.
When __main__ runs different variables get assigned. In particular opts,
scc, sl and ch. Those variables are available in any scope and
technically it is possible to access them. However, lets not do this
since it leads to confusion. Also, pylint will complain about those code
- Let's use the proper locations (sl and ch are stored in PysimApp.
- Scc can be assigned in init_card.
- In method walk, the use of the variable opts to call ection_df is wrong,
lets use **kwargs (see also usage of action_ef).
- The constructor of Cmd2ApduTracer has a parameter cmd2_app, but usese
the global variable app. Let's use cmd2_app instead.
- Do not use opts.num in find_row_in_csv_file, use num instead.
- Pass scc to process_card as parameter so that it won't access scc
in the global scope.
In some cases, the specs do not specify an absolute record length.
Instead there may be only a minimum record length specified. The card
vendor may then chose to use larger record length at will. This usually
is no problem since the data is usually written from the left and the
remaining bytes are padded at the end (right side) of the data. However
in some rare cases (EF.MSISDN, see also 3GPP TS 51.011, section 10.5.5)
the data must be written right-aligned towards the physical record
length. This means that the data is padded from the left in this case.
To fix this: Let's add a "leftpad" flag to LinFixedEF, which we set to
true in those corner cases. The code that updates the record in
commands.py must then check this flag and padd the data accordingly.
The methods verify_binary and verify_record are only used internally
in class SimCardCommands, they can be both private methods. Also lets
move them above the method that uses them.