laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/33699 )
Change subject: Introduce concept of CardProfileAddon
......................................................................
Introduce concept of CardProfileAddon
We have a strict "one CardProfile per card" rule. For a modern UICC
without legacy SIM support, that works great, as all applications
have AID and ADF and can hence be enumerated/detected that way.
However, in reality there are mostly UICC that have legacy SIM, GSM-R
or even CDMA support, all of which are not proper UICC applications
for historical reasons.
So instead of having hard-coded hacks in various places, let's introduce
the new concept of a CardProfileAddon. Every profile can have any
number of those. When building up the RuntimeState, we iterate over the
CardProfile addons, and probe which of those are actually on the card.
For those discovered, we add their files to the filesystem hierarchy.
Change-Id: I5866590b6d48f85eb889c9b1b8ab27936d2378b9
---
M pySim-shell.py
M pySim-trace.py
M pySim/cdma_ruim.py
M pySim/filesystem.py
M pySim/gsm_r.py
M pySim/profile.py
M pySim/ts_102_221.py
M pySim/ts_51_011.py
8 files changed, 128 insertions(+), 15 deletions(-)
Approvals:
Jenkins Builder: Verified
laforge: Looks good to me, approved
diff --git a/pySim-shell.py b/pySim-shell.py
index 4d4f2e2..b48abc7 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -137,11 +137,6 @@
# Create runtime state with card profile
rs = RuntimeState(card, profile)
- # FIXME: This is an GSM-R related file, it needs to be added throughout,
- # the profile. At the moment we add it for all cards, this won't hurt,
- # but regular SIM and UICC will not have it and fail to select it.
- rs.mf.add_file(DF_EIRENE())
-
CardModel.apply_matching_models(scc, rs)
# inform the transport that we can do context-specific SW interpretation
diff --git a/pySim-trace.py b/pySim-trace.py
index 4c8696d..ba1568c 100755
--- a/pySim-trace.py
+++ b/pySim-trace.py
@@ -11,7 +11,7 @@
from pySim.cards import UiccCardBase
from pySim.commands import SimCardCommands
from pySim.profile import CardProfile
-from pySim.ts_102_221 import CardProfileUICCSIM
+from pySim.ts_102_221 import CardProfileUICC
from pySim.ts_31_102 import CardApplicationUSIM
from pySim.ts_31_103 import CardApplicationISIM
from pySim.transport import LinkBase
@@ -70,8 +70,9 @@
class Tracer:
def __init__(self, **kwargs):
- # we assume a generic SIM + UICC + USIM + ISIM card
- profile = CardProfileUICCSIM()
+ # we assume a generic UICC profile; as all APDUs return 9000 in DummySimLink above,
+ # all CardProfileAddon (including SIM) will probe successful.
+ profile = CardProfileUICC()
profile.add_application(CardApplicationUSIM())
profile.add_application(CardApplicationISIM())
scc = SimCardCommands(transport=DummySimLink())
diff --git a/pySim/cdma_ruim.py b/pySim/cdma_ruim.py
index 8b66490..b254403 100644
--- a/pySim/cdma_ruim.py
+++ b/pySim/cdma_ruim.py
@@ -22,7 +22,7 @@
from pySim.utils import *
from pySim.filesystem import *
from pySim.profile import match_ruim
-from pySim.profile import CardProfile
+from pySim.profile import CardProfile, CardProfileAddon
from pySim.ts_51_011 import CardProfileSIM
from pySim.ts_51_011 import DF_TELECOM, DF_GSM
from pySim.ts_51_011 import EF_ServiceTable
@@ -191,3 +191,14 @@
@staticmethod
def match_with_card(scc: SimCardCommands) -> bool:
return match_ruim(scc)
+
+class AddonRUIM(CardProfileAddon):
+ """An Addon that can be found on on a combined SIM + RUIM or UICC + RUIM to support CDMA."""
+ def __init__(self):
+ files = [
+ DF_CDMA()
+ ]
+ super().__init__('RUIM', desc='CDMA RUIM', files_in_mf=files)
+
+ def probe(self, card: 'CardBase') -> bool:
+ return card.file_exists(self.files_in_mf[0].fid)
diff --git a/pySim/filesystem.py b/pySim/filesystem.py
index 22ff60d..cb3b403 100644
--- a/pySim/filesystem.py
+++ b/pySim/filesystem.py
@@ -1307,6 +1307,16 @@
self.card.set_apdu_parameter(
cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
+ for addon_cls in self.profile.addons:
+ addon = addon_cls()
+ if addon.probe(self.card):
+ print("Detected %s Add-on \"%s\"" % (self.profile, addon))
+ for f in addon.files_in_mf:
+ self.mf.add_file(f)
+
+ # go back to MF before the next steps (addon probing might have changed DF)
+ self.card._scc.select_file('3F00')
+
# add application ADFs + MF-files from profile
apps = self._match_applications()
for a in apps:
diff --git a/pySim/gsm_r.py b/pySim/gsm_r.py
index 389a8cb..cd111d6 100644
--- a/pySim/gsm_r.py
+++ b/pySim/gsm_r.py
@@ -35,8 +35,8 @@
from pySim.construct import *
import enum
+from pySim.profile import CardProfileAddon
from pySim.filesystem import *
-import pySim.ts_102_221
import pySim.ts_51_011
######################################################################
@@ -362,3 +362,15 @@
desc='Free Number Call Type 0 and 8'),
]
self.add_files(files)
+
+
+class AddonGSMR(CardProfileAddon):
+ """An Addon that can be found on either classic GSM SIM or on UICC to support GSM-R."""
+ def __init__(self):
+ files = [
+ DF_EIRENE()
+ ]
+ super().__init__('GSM-R', desc='Railway GSM', files_in_mf=files)
+
+ def probe(self, card: 'CardBase') -> bool:
+ return card.file_exists(self.files_in_mf[0].fid)
diff --git a/pySim/profile.py b/pySim/profile.py
index e464e1f..0d09e81 100644
--- a/pySim/profile.py
+++ b/pySim/profile.py
@@ -88,6 +88,7 @@
shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
cla : class byte that should be used with cards of this profile
sel_ctrl : selection control bytes class byte that should be used with cards of this profile
+ addons: List of optional CardAddons that a card of this profile might have
"""
self.name = name
self.desc = kw.get("desc", None)
@@ -97,6 +98,8 @@
self.shell_cmdsets = kw.get("shell_cmdsets", [])
self.cla = kw.get("cla", "00")
self.sel_ctrl = kw.get("sel_ctrl", "0004")
+ # list of optional addons that a card of this profile might have
+ self.addons = kw.get("addons", [])
def __str__(self):
return self.name
@@ -161,3 +164,34 @@
return p()
return None
+
+ def add_addon(self, addon: 'CardProfileAddon'):
+ assert(addon not in self.addons)
+ # we don't install any additional files, as that is happening in the RuntimeState.
+ self.addons.append(addon)
+
+class CardProfileAddon(abc.ABC):
+ """A Card Profile Add-on is something that is not a card application or a full stand-alone
+ card profile, but an add-on to an existing profile. Think of GSM-R specific files existing
+ on what is otherwise a SIM or USIM+SIM card."""
+
+ def __init__(self, name: str, **kw):
+ """
+ Args:
+ desc (str) : Description
+ files_in_mf : List of CardEF instances present in MF
+ shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
+ """
+ self.name = name
+ self.desc = kw.get("desc", None)
+ self.files_in_mf = kw.get("files_in_mf", [])
+ self.shell_cmdsets = kw.get("shell_cmdsets", [])
+ pass
+
+ def __str__(self):
+ return self.name
+
+ @abc.abstractmethod
+ def probe(self, card: 'CardBase') -> bool:
+ """Probe a given card to determine whether or not this add-on is present/supported."""
+ pass
diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py
index b6c003b..df8b842 100644
--- a/pySim/ts_102_221.py
+++ b/pySim/ts_102_221.py
@@ -31,7 +31,9 @@
# A UICC will usually also support 2G functionality. If this is the case, we
# need to add DF_GSM and DF_TELECOM along with the UICC related files
-from pySim.ts_51_011 import DF_GSM, DF_TELECOM
+from pySim.ts_51_011 import DF_GSM, DF_TELECOM, AddonSIM
+from pySim.gsm_r import AddonGSMR
+from pySim.cdma_ruim import AddonRUIM
ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [
# TS 102 221 Section 10.1.2 Table 10.5 "Coding of Instruction Byte"
@@ -768,6 +770,11 @@
# FIXME: DF.CD
EF_UMPC(),
]
+ addons = [
+ AddonSIM,
+ AddonGSMR,
+ AddonRUIM,
+ ]
sw = {
'Normal': {
'9000': 'Normal ending of the command',
@@ -839,7 +846,7 @@
super().__init__(name, desc='ETSI TS 102 221', cla="00",
sel_ctrl="0004", files_in_mf=files, sw=sw,
- shell_cmdsets = [self.AddlShellCommands()])
+ shell_cmdsets = [self.AddlShellCommands()], addons = addons)
@staticmethod
def decode_select_response(resp_hex: str) -> object:
@@ -895,4 +902,5 @@
@staticmethod
def match_with_card(scc: SimCardCommands) -> bool:
- return match_uicc(scc) and match_sim(scc)
+ # don't ever select this profile, we only use this from pySim-trace
+ return False
diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py
index 1f98e72..c81bfdf 100644
--- a/pySim/ts_51_011.py
+++ b/pySim/ts_51_011.py
@@ -30,9 +30,10 @@
#
from pySim.profile import match_sim
-from pySim.profile import CardProfile
+from pySim.profile import CardProfile, CardProfileAddon
from pySim.filesystem import *
from pySim.ts_31_102_telecom import DF_PHONEBOOK, DF_MULTIMEDIA, DF_MCS, DF_V2X
+from pySim.gsm_r import AddonGSMR
import enum
from pySim.construct import *
from construct import Optional as COptional
@@ -1047,8 +1048,12 @@
},
}
+ addons = [
+ AddonGSMR,
+ ]
+
super().__init__('SIM', desc='GSM SIM Card', cla="a0",
- sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw)
+ sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw, addons = addons)
@staticmethod
def decode_select_response(resp_hex: str) -> object:
@@ -1104,3 +1109,17 @@
@staticmethod
def match_with_card(scc: SimCardCommands) -> bool:
return match_sim(scc)
+
+
+class AddonSIM(CardProfileAddon):
+ """An add-on that can be found on a UICC in order to support classic GSM SIM."""
+ def __init__(self):
+ files = [
+ DF_GSM(),
+ DF_TELECOM(),
+ ]
+ super().__init__('SIM', desc='GSM SIM', files_in_mf=files)
+
+ def probe(self, card:'CardBase') -> bool:
+ # we assume the add-on to be present in case DF.GSM is found on the card
+ return card.file_exists(self.files_in_mf[0].fid)
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/33699
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I5866590b6d48f85eb889c9b1b8ab27936d2378b9
Gerrit-Change-Number: 33699
Gerrit-PatchSet: 6
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-MessageType: merged
laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/33705 )
(
1 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted one.
)Change subject: pySim-shell: Add a mode where a pySim-shell cmd can be passed by shell
......................................................................
pySim-shell: Add a mode where a pySim-shell cmd can be passed by shell
This adds a new operation mode for pySim-shell, where a single command
can be passed to pySim-shell, which then is executed before pySim-shell
terminates.
Example: ./pySim-shell.py -p0 export --json
Change-Id: I0ed379b23a4b1126006fd8f9e7ba2ba07fb01ada
Closes: OS#6088
---
M pySim-shell.py
1 file changed, 25 insertions(+), 1 deletion(-)
Approvals:
dexter: Looks good to me, approved
fixeria: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/pySim-shell.py b/pySim-shell.py
index 213f057..3b56bfd 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -982,6 +982,11 @@
adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
help='ADM PIN used for provisioning, as hex string (16 characters long)')
+option_parser.add_argument("command", nargs='?',
+ help="A pySim-shell command that would optionally be executed at startup")
+option_parser.add_argument('command_args', nargs=argparse.REMAINDER,
+ help="Optional Arguments for command")
+
if __name__ == '__main__':
@@ -1045,4 +1050,7 @@
except Exception as e:
print(e)
- app.cmdloop()
+ if opts.command:
+ app.onecmd_plus_hooks('{} {}'.format(opts.command, ' '.join(opts.command_args)))
+ else:
+ app.cmdloop()
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/33705
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I0ed379b23a4b1126006fd8f9e7ba2ba07fb01ada
Gerrit-Change-Number: 33705
Gerrit-PatchSet: 3
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: merged
laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/33702 )
(
2 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted one.
)Change subject: move Runtime{State,Lchan} from pySim.filesystem to new pySim.runtime
......................................................................
move Runtime{State,Lchan} from pySim.filesystem to new pySim.runtime
Those two are really separate concepts, so let's keep them in separate
source code files.
Change-Id: I9ec54304dd8f4a4cba9487054a8eb8d265c2d340
---
M pySim-shell.py
M pySim-trace.py
M pySim/apdu/__init__.py
M pySim/apdu/ts_102_221.py
M pySim/filesystem.py
A pySim/runtime.py
M pySim/sysmocom_sja2.py
7 files changed, 537 insertions(+), 497 deletions(-)
Approvals:
dexter: Looks good to me, approved
fixeria: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/pySim-shell.py b/pySim-shell.py
index 5281eab..213f057 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -55,7 +55,8 @@
from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, Hexstr
from pySim.card_handler import CardHandler, CardHandlerAuto
-from pySim.filesystem import RuntimeState, CardDF, CardADF, CardModel, CardApplication
+from pySim.filesystem import CardDF, CardADF, CardModel, CardApplication
+from pySim.runtime import RuntimeState
from pySim.profile import CardProfile
from pySim.cdma_ruim import CardProfileRUIM
from pySim.ts_102_221 import CardProfileUICC
diff --git a/pySim-trace.py b/pySim-trace.py
index ba1568c..d457bf4 100755
--- a/pySim-trace.py
+++ b/pySim-trace.py
@@ -6,7 +6,7 @@
from pprint import pprint as pp
from pySim.apdu import *
-from pySim.filesystem import RuntimeState
+from pySim.runtime import RuntimeState
from pySim.cards import UiccCardBase
from pySim.commands import SimCardCommands
diff --git a/pySim/apdu/__init__.py b/pySim/apdu/__init__.py
index 1d1174d..cc0f701 100644
--- a/pySim/apdu/__init__.py
+++ b/pySim/apdu/__init__.py
@@ -35,7 +35,7 @@
from construct import Optional as COptional
from pySim.construct import *
from pySim.utils import *
-from pySim.filesystem import RuntimeLchan, RuntimeState, lchan_nr_from_cla
+from pySim.runtime import RuntimeLchan, RuntimeState, lchan_nr_from_cla
from pySim.filesystem import CardADF, CardFile, TransparentEF, LinFixedEF
"""There are multiple levels of decode:
diff --git a/pySim/apdu/ts_102_221.py b/pySim/apdu/ts_102_221.py
index cd246b1..3780c8a 100644
--- a/pySim/apdu/ts_102_221.py
+++ b/pySim/apdu/ts_102_221.py
@@ -20,6 +20,7 @@
import logging
from pySim.construct import *
from pySim.filesystem import *
+from pySim.runtime import RuntimeLchan
from pySim.apdu import ApduCommand, ApduCommandSet
from typing import Optional, Dict, Tuple
diff --git a/pySim/filesystem.py b/pySim/filesystem.py
index 2bcd363..9f3ee17 100644
--- a/pySim/filesystem.py
+++ b/pySim/filesystem.py
@@ -51,18 +51,6 @@
Size = Tuple[int, Optional[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.
@@ -1285,486 +1273,6 @@
self.size = size
self.shell_commands = [self.ShellCommands()]
-
-class RuntimeState:
- """Represent the runtime state of a session with a card."""
-
- def __init__(self, card, profile: 'CardProfile'):
- """
- Args:
- card : pysim.cards.Card instance
- profile : CardProfile instance
- """
- self.mf = CardMF(profile=profile)
- self.card = card
- self.profile = profile
- 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
- self.card.set_apdu_parameter(
- cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
-
- for addon_cls in self.profile.addons:
- addon = addon_cls()
- if addon.probe(self.card):
- print("Detected %s Add-on \"%s\"" % (self.profile, addon))
- for f in addon.files_in_mf:
- self.mf.add_file(f)
-
- # go back to MF before the next steps (addon probing might have changed DF)
- self.card._scc.select_file('3F00')
-
- # add application ADFs + MF-files from profile
- apps = self._match_applications()
- for a in apps:
- if a.adf:
- self.mf.add_application_df(a.adf)
- for f in self.profile.files_in_mf:
- self.mf.add_file(f)
- self.conserve_write = True
-
- # make sure that when the runtime state is created, the card is also
- # in a defined state.
- self.reset()
-
- def _match_applications(self):
- """match the applications from the profile with applications on the card"""
- apps_profile = self.profile.applications
-
- # When the profile does not feature any applications, then we are done already
- if not apps_profile:
- return []
-
- # Read AIDs from card and match them against the applications defined by the
- # card profile
- aids_card = self.card.read_aids()
- apps_taken = []
- if aids_card:
- aids_taken = []
- print("AIDs on card:")
- for a in aids_card:
- for f in apps_profile:
- if f.aid in a:
- print(" %s: %s (EF.DIR)" % (f.name, a))
- aids_taken.append(a)
- apps_taken.append(f)
- aids_unknown = set(aids_card) - set(aids_taken)
- for a in aids_unknown:
- print(" unknown: %s (EF.DIR)" % a)
- else:
- print("warning: EF.DIR seems to be empty!")
-
- # Some card applications may not be registered in EF.DIR, we will actively
- # probe for those applications
- for f in set(apps_profile) - set(apps_taken):
- try:
- data, sw = self.card.select_adf_by_aid(f.aid)
- if sw == "9000":
- print(" %s: %s" % (f.name, f.aid))
- apps_taken.append(f)
- except (SwMatchError, ProtocolError):
- 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']
-
- def selected_file_shareable(self) -> bool:
- return self.selected_file_descriptor_byte()['shareable']
-
- def selected_file_structure(self) -> str:
- return self.selected_file_descriptor_byte()['structure']
-
- def selected_file_type(self) -> str:
- return self.selected_file_descriptor_byte()['file_type']
-
- def selected_file_num_of_rec(self) -> Optional[int]:
- return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
-
- def get_cwd(self) -> CardDF:
- """Obtain the current working directory.
-
- Returns:
- CardDF instance
- """
- if isinstance(self.selected_file, CardDF):
- return self.selected_file
- else:
- return self.selected_file.parent
-
- def get_application_df(self) -> Optional[CardADF]:
- """Obtain the currently selected application DF (if any).
-
- Returns:
- CardADF() instance or None"""
- # iterate upwards from selected file; check if any is an ADF
- node = self.selected_file
- while node.parent != node:
- if isinstance(node, CardADF):
- return node
- node = node.parent
- return None
-
- def interpret_sw(self, sw: str):
- """Interpret a given status word relative to the currently selected application
- or the underlying card profile.
-
- Args:
- sw : Status word as string of 4 hex digits
-
- Returns:
- Tuple of two strings
- """
- res = None
- adf = self.get_application_df()
- if adf:
- app = adf.application
- # The application either comes with its own interpret_sw
- # method or we will use the interpret_sw method from the
- # card profile.
- if app and hasattr(app, "interpret_sw"):
- res = app.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
- object if the file actually exists."""
- if not is_hex(fid, 4, 4):
- raise ValueError(
- "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
-
- try:
- (data, sw) = self.rs.card._scc.select_file(fid)
- except SwMatchError as swm:
- k = self.interpret_sw(swm.sw_actual)
- if not k:
- raise(swm)
- raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
-
- select_resp = self.selected_file.decode_select_response(data)
- if (select_resp['file_descriptor']['file_descriptor_byte']['file_type'] == 'df'):
- f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
- desc="dedicated file, manually added at runtime")
- else:
- if (select_resp['file_descriptor']['file_descriptor_byte']['structure'] == 'transparent'):
- f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
- desc="elementary file, manually added at runtime")
- else:
- f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
- desc="elementary file, manually added at runtime")
-
- self.selected_file.add_files([f])
- self.selected_file = f
- return select_resp, data
-
- def _select_pre(self, cmd_app):
- # unregister commands of old file
- if cmd_app and self.selected_file.shell_commands:
- for c in self.selected_file.shell_commands:
- cmd_app.unregister_command_set(c)
-
- def _select_post(self, cmd_app):
- # register commands of new file
- if cmd_app and self.selected_file.shell_commands:
- for c in self.selected_file.shell_commands:
- cmd_app.register_command_set(c)
-
- def select_file(self, file: CardFile, cmd_app=None):
- """Select a file (EF, DF, ADF, MF, ...).
-
- Args:
- file : CardFile [or derived class] instance
- cmd_app : Command Application State (for unregistering old file commands)
- """
- # we need to find a path from our self.selected_file to the destination
- inter_path = self.selected_file.build_select_path_to(file)
- if not inter_path:
- raise RuntimeError('Cannot determine path from %s to %s' % (self.selected_file, file))
-
- self._select_pre(cmd_app)
-
- for p in inter_path:
- try:
- if isinstance(p, CardADF):
- (data, sw) = self.rs.card.select_adf_by_aid(p.aid)
- self.selected_adf = p
- else:
- (data, sw) = self.rs.card._scc.select_file(p.fid)
- self.selected_file = p
- except SwMatchError as swm:
- self._select_post(cmd_app)
- raise(swm)
-
- self._select_post(cmd_app)
-
- def select(self, name: str, cmd_app=None):
- """Select a file (EF, DF, ADF, MF, ...).
-
- Args:
- name : Name of file to select
- cmd_app : Command Application State (for unregistering old file commands)
- """
- # handling of entire paths with multiple directories/elements
- if '/' in name:
- prev_sel_file = self.selected_file
- pathlist = name.split('/')
- # treat /DF.GSM/foo like MF/DF.GSM/foo
- if pathlist[0] == '':
- pathlist[0] = 'MF'
- try:
- for p in pathlist:
- self.select(p, cmd_app)
- return
- except Exception as e:
- # if any intermediate step fails, go back to where we were
- self.select_file(prev_sel_file, cmd_app)
- raise e
-
- sels = self.selected_file.get_selectables()
- if is_hex(name):
- name = name.lower()
-
- self._select_pre(cmd_app)
-
- if name in sels:
- f = sels[name]
- try:
- if isinstance(f, CardADF):
- (data, sw) = self.rs.card.select_adf_by_aid(f.aid)
- else:
- (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)
- if not k:
- raise(swm)
- raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
- select_resp = f.decode_select_response(data)
- else:
- (select_resp, data) = self.probe_file(name, cmd_app)
-
- # store the raw + decoded FCP for later reference
- self.selected_file_fcp_hex = data
- self.selected_file_fcp = select_resp
-
- self._select_post(cmd_app)
- return select_resp
-
- def status(self):
- """Request STATUS (current selected file FCP) from card."""
- (data, sw) = self.rs.card._scc.status()
- return self.selected_file.decode_select_response(data)
-
- def get_file_for_selectable(self, name: str):
- sels = self.selected_file.get_selectables()
- return sels[name]
-
- def activate_file(self, name: str):
- """Request ACTIVATE FILE of specified file."""
- sels = self.selected_file.get_selectables()
- f = sels[name]
- data, sw = self.rs.card._scc.activate_file(f.fid)
- return data, sw
-
- def read_binary(self, length: int = None, offset: int = 0):
- """Read [part of] a transparent EF binary data.
-
- Args:
- length : Amount of data to read (None: as much as possible)
- offset : Offset into the file from which to read 'length' bytes
- Returns:
- binary data read from the file
- """
- if not isinstance(self.selected_file, TransparentEF):
- raise TypeError("Only works with TransparentEF")
- 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.
-
- Args:
- length : Amount of data to read (None: as much as possible)
- offset : Offset into the file from which to read 'length' bytes
- Returns:
- abstract decode data read from the file
- """
- (data, sw) = self.read_binary()
- dec_data = self.selected_file.decode_hex(data)
- return (dec_data, sw)
-
- def update_binary(self, data_hex: str, offset: int = 0):
- """Update transparent EF binary data.
-
- Args:
- data_hex : hex string of data to be written
- offset : Offset into the file from which to write 'data_hex'
- """
- if not isinstance(self.selected_file, TransparentEF):
- raise TypeError("Only works with TransparentEF")
- 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
- then updates the EF with it.
-
- Args:
- data : abstract data which is to be encoded and written
- """
- data_hex = self.selected_file.encode_hex(data)
- return self.update_binary(data_hex)
-
- def read_record(self, rec_nr: int = 0):
- """Read a record as binary data.
-
- Args:
- rec_nr : Record number to read
- Returns:
- hex string of binary data contained in record
- """
- if not isinstance(self.selected_file, LinFixedEF):
- raise TypeError("Only works with Linear Fixed EF")
- # returns a string of hex nibbles
- 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.
-
- Args:
- rec_nr : Record number to read
- Returns:
- abstract data contained in record
- """
- (data, sw) = self.read_record(rec_nr)
- return (self.selected_file.decode_record_hex(data, rec_nr), sw)
-
- def update_record(self, rec_nr: int, data_hex: str):
- """Update a record with given binary data
-
- Args:
- rec_nr : Record number to read
- data_hex : Hex string binary data to be written
- """
- if not isinstance(self.selected_file, LinFixedEF):
- raise TypeError("Only works with Linear Fixed EF")
- 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
- and then write it to the given record on the card.
-
- Args:
- rec_nr : Record number to read
- data_hex : Abstract data to be written
- """
- data_hex = self.selected_file.encode_record_hex(data, rec_nr)
- return self.update_record(rec_nr, data_hex)
-
- def retrieve_data(self, tag: int = 0):
- """Read a DO/TLV as binary data.
-
- Args:
- tag : Tag of TLV/DO to read
- Returns:
- hex string of full BER-TLV DO including Tag and Length
- """
- if not isinstance(self.selected_file, BerTlvEF):
- raise TypeError("Only works with BER-TLV EF")
- # returns a string of hex nibbles
- return self.rs.card._scc.retrieve_data(self.selected_file.fid, tag)
-
- def retrieve_tags(self):
- """Retrieve tags available on BER-TLV EF.
-
- Returns:
- list of integer tags contained in EF
- """
- if not isinstance(self.selected_file, BerTlvEF):
- raise TypeError("Only works with BER-TLV EF")
- 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)
-
- def set_data(self, tag: int, data_hex: str):
- """Update a TLV/DO with given binary data
-
- Args:
- tag : Tag of TLV/DO to be written
- data_hex : Hex string binary data to be written (value portion)
- """
- if not isinstance(self.selected_file, BerTlvEF):
- raise TypeError("Only works with BER-TLV EF")
- 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."""
- if cmd_app and self.selected_file.shell_commands:
- for c in self.selected_file.shell_commands:
- cmd_app.unregister_command_set(c)
-
-
def interpret_sw(sw_data: dict, sw: str):
"""Interpret a given status word.
@@ -1828,7 +1336,7 @@
@classmethod
@abc.abstractmethod
- def add_files(cls, rs: RuntimeState):
+ def add_files(cls, rs: 'RuntimeState'):
"""Add model specific files to given RuntimeState."""
@classmethod
@@ -1843,7 +1351,7 @@
return False
@staticmethod
- def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
+ def apply_matching_models(scc: SimCardCommands, rs: 'RuntimeState'):
"""Check if any of the CardModel sub-classes 'match' the currently inserted card
(by ATR or overriding the 'match' method). If so, call their 'add_files'
method."""
diff --git a/pySim/runtime.py b/pySim/runtime.py
new file mode 100644
index 0000000..5d181e8
--- /dev/null
+++ b/pySim/runtime.py
@@ -0,0 +1,517 @@
+# coding=utf-8
+"""Representation of the runtime state of an application like pySim-shell.
+"""
+
+# (C) 2021 by Harald Welte <laforge(a)osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from typing import Optional, Tuple
+
+from pySim.utils import sw_match, h2b, i2h, is_hex, bertlv_parse_one, Hexstr
+from pySim.exceptions import *
+from pySim.filesystem import *
+
+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 RuntimeState:
+ """Represent the runtime state of a session with a card."""
+
+ def __init__(self, card, profile: 'CardProfile'):
+ """
+ Args:
+ card : pysim.cards.Card instance
+ profile : CardProfile instance
+ """
+ self.mf = CardMF(profile=profile)
+ self.card = card
+ self.profile = profile
+ 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
+ self.card.set_apdu_parameter(
+ cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
+
+ for addon_cls in self.profile.addons:
+ addon = addon_cls()
+ if addon.probe(self.card):
+ print("Detected %s Add-on \"%s\"" % (self.profile, addon))
+ for f in addon.files_in_mf:
+ self.mf.add_file(f)
+
+ # go back to MF before the next steps (addon probing might have changed DF)
+ self.card._scc.select_file('3F00')
+
+ # add application ADFs + MF-files from profile
+ apps = self._match_applications()
+ for a in apps:
+ if a.adf:
+ self.mf.add_application_df(a.adf)
+ for f in self.profile.files_in_mf:
+ self.mf.add_file(f)
+ self.conserve_write = True
+
+ # make sure that when the runtime state is created, the card is also
+ # in a defined state.
+ self.reset()
+
+ def _match_applications(self):
+ """match the applications from the profile with applications on the card"""
+ apps_profile = self.profile.applications
+
+ # When the profile does not feature any applications, then we are done already
+ if not apps_profile:
+ return []
+
+ # Read AIDs from card and match them against the applications defined by the
+ # card profile
+ aids_card = self.card.read_aids()
+ apps_taken = []
+ if aids_card:
+ aids_taken = []
+ print("AIDs on card:")
+ for a in aids_card:
+ for f in apps_profile:
+ if f.aid in a:
+ print(" %s: %s (EF.DIR)" % (f.name, a))
+ aids_taken.append(a)
+ apps_taken.append(f)
+ aids_unknown = set(aids_card) - set(aids_taken)
+ for a in aids_unknown:
+ print(" unknown: %s (EF.DIR)" % a)
+ else:
+ print("warning: EF.DIR seems to be empty!")
+
+ # Some card applications may not be registered in EF.DIR, we will actively
+ # probe for those applications
+ for f in set(apps_profile) - set(apps_taken):
+ try:
+ data, sw = self.card.select_adf_by_aid(f.aid)
+ if sw == "9000":
+ print(" %s: %s" % (f.name, f.aid))
+ apps_taken.append(f)
+ except (SwMatchError, ProtocolError):
+ 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']
+
+ def selected_file_shareable(self) -> bool:
+ return self.selected_file_descriptor_byte()['shareable']
+
+ def selected_file_structure(self) -> str:
+ return self.selected_file_descriptor_byte()['structure']
+
+ def selected_file_type(self) -> str:
+ return self.selected_file_descriptor_byte()['file_type']
+
+ def selected_file_num_of_rec(self) -> Optional[int]:
+ return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
+
+ def get_cwd(self) -> CardDF:
+ """Obtain the current working directory.
+
+ Returns:
+ CardDF instance
+ """
+ if isinstance(self.selected_file, CardDF):
+ return self.selected_file
+ else:
+ return self.selected_file.parent
+
+ def get_application_df(self) -> Optional[CardADF]:
+ """Obtain the currently selected application DF (if any).
+
+ Returns:
+ CardADF() instance or None"""
+ # iterate upwards from selected file; check if any is an ADF
+ node = self.selected_file
+ while node.parent != node:
+ if isinstance(node, CardADF):
+ return node
+ node = node.parent
+ return None
+
+ def interpret_sw(self, sw: str):
+ """Interpret a given status word relative to the currently selected application
+ or the underlying card profile.
+
+ Args:
+ sw : Status word as string of 4 hex digits
+
+ Returns:
+ Tuple of two strings
+ """
+ res = None
+ adf = self.get_application_df()
+ if adf:
+ app = adf.application
+ # The application either comes with its own interpret_sw
+ # method or we will use the interpret_sw method from the
+ # card profile.
+ if app and hasattr(app, "interpret_sw"):
+ res = app.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
+ object if the file actually exists."""
+ if not is_hex(fid, 4, 4):
+ raise ValueError(
+ "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
+
+ try:
+ (data, sw) = self.rs.card._scc.select_file(fid)
+ except SwMatchError as swm:
+ k = self.interpret_sw(swm.sw_actual)
+ if not k:
+ raise(swm)
+ raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
+
+ select_resp = self.selected_file.decode_select_response(data)
+ if (select_resp['file_descriptor']['file_descriptor_byte']['file_type'] == 'df'):
+ f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
+ desc="dedicated file, manually added at runtime")
+ else:
+ if (select_resp['file_descriptor']['file_descriptor_byte']['structure'] == 'transparent'):
+ f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
+ desc="elementary file, manually added at runtime")
+ else:
+ f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
+ desc="elementary file, manually added at runtime")
+
+ self.selected_file.add_files([f])
+ self.selected_file = f
+ return select_resp, data
+
+ def _select_pre(self, cmd_app):
+ # unregister commands of old file
+ if cmd_app and self.selected_file.shell_commands:
+ for c in self.selected_file.shell_commands:
+ cmd_app.unregister_command_set(c)
+
+ def _select_post(self, cmd_app):
+ # register commands of new file
+ if cmd_app and self.selected_file.shell_commands:
+ for c in self.selected_file.shell_commands:
+ cmd_app.register_command_set(c)
+
+ def select_file(self, file: CardFile, cmd_app=None):
+ """Select a file (EF, DF, ADF, MF, ...).
+
+ Args:
+ file : CardFile [or derived class] instance
+ cmd_app : Command Application State (for unregistering old file commands)
+ """
+ # we need to find a path from our self.selected_file to the destination
+ inter_path = self.selected_file.build_select_path_to(file)
+ if not inter_path:
+ raise RuntimeError('Cannot determine path from %s to %s' % (self.selected_file, file))
+
+ self._select_pre(cmd_app)
+
+ for p in inter_path:
+ try:
+ if isinstance(p, CardADF):
+ (data, sw) = self.rs.card.select_adf_by_aid(p.aid)
+ self.selected_adf = p
+ else:
+ (data, sw) = self.rs.card._scc.select_file(p.fid)
+ self.selected_file = p
+ except SwMatchError as swm:
+ self._select_post(cmd_app)
+ raise(swm)
+
+ self._select_post(cmd_app)
+
+ def select(self, name: str, cmd_app=None):
+ """Select a file (EF, DF, ADF, MF, ...).
+
+ Args:
+ name : Name of file to select
+ cmd_app : Command Application State (for unregistering old file commands)
+ """
+ # handling of entire paths with multiple directories/elements
+ if '/' in name:
+ prev_sel_file = self.selected_file
+ pathlist = name.split('/')
+ # treat /DF.GSM/foo like MF/DF.GSM/foo
+ if pathlist[0] == '':
+ pathlist[0] = 'MF'
+ try:
+ for p in pathlist:
+ self.select(p, cmd_app)
+ return
+ except Exception as e:
+ # if any intermediate step fails, go back to where we were
+ self.select_file(prev_sel_file, cmd_app)
+ raise e
+
+ sels = self.selected_file.get_selectables()
+ if is_hex(name):
+ name = name.lower()
+
+ self._select_pre(cmd_app)
+
+ if name in sels:
+ f = sels[name]
+ try:
+ if isinstance(f, CardADF):
+ (data, sw) = self.rs.card.select_adf_by_aid(f.aid)
+ else:
+ (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)
+ if not k:
+ raise(swm)
+ raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
+ select_resp = f.decode_select_response(data)
+ else:
+ (select_resp, data) = self.probe_file(name, cmd_app)
+
+ # store the raw + decoded FCP for later reference
+ self.selected_file_fcp_hex = data
+ self.selected_file_fcp = select_resp
+
+ self._select_post(cmd_app)
+ return select_resp
+
+ def status(self):
+ """Request STATUS (current selected file FCP) from card."""
+ (data, sw) = self.rs.card._scc.status()
+ return self.selected_file.decode_select_response(data)
+
+ def get_file_for_selectable(self, name: str):
+ sels = self.selected_file.get_selectables()
+ return sels[name]
+
+ def activate_file(self, name: str):
+ """Request ACTIVATE FILE of specified file."""
+ sels = self.selected_file.get_selectables()
+ f = sels[name]
+ data, sw = self.rs.card._scc.activate_file(f.fid)
+ return data, sw
+
+ def read_binary(self, length: int = None, offset: int = 0):
+ """Read [part of] a transparent EF binary data.
+
+ Args:
+ length : Amount of data to read (None: as much as possible)
+ offset : Offset into the file from which to read 'length' bytes
+ Returns:
+ binary data read from the file
+ """
+ if not isinstance(self.selected_file, TransparentEF):
+ raise TypeError("Only works with TransparentEF")
+ 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.
+
+ Args:
+ length : Amount of data to read (None: as much as possible)
+ offset : Offset into the file from which to read 'length' bytes
+ Returns:
+ abstract decode data read from the file
+ """
+ (data, sw) = self.read_binary()
+ dec_data = self.selected_file.decode_hex(data)
+ return (dec_data, sw)
+
+ def update_binary(self, data_hex: str, offset: int = 0):
+ """Update transparent EF binary data.
+
+ Args:
+ data_hex : hex string of data to be written
+ offset : Offset into the file from which to write 'data_hex'
+ """
+ if not isinstance(self.selected_file, TransparentEF):
+ raise TypeError("Only works with TransparentEF")
+ 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
+ then updates the EF with it.
+
+ Args:
+ data : abstract data which is to be encoded and written
+ """
+ data_hex = self.selected_file.encode_hex(data)
+ return self.update_binary(data_hex)
+
+ def read_record(self, rec_nr: int = 0):
+ """Read a record as binary data.
+
+ Args:
+ rec_nr : Record number to read
+ Returns:
+ hex string of binary data contained in record
+ """
+ if not isinstance(self.selected_file, LinFixedEF):
+ raise TypeError("Only works with Linear Fixed EF")
+ # returns a string of hex nibbles
+ 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.
+
+ Args:
+ rec_nr : Record number to read
+ Returns:
+ abstract data contained in record
+ """
+ (data, sw) = self.read_record(rec_nr)
+ return (self.selected_file.decode_record_hex(data, rec_nr), sw)
+
+ def update_record(self, rec_nr: int, data_hex: str):
+ """Update a record with given binary data
+
+ Args:
+ rec_nr : Record number to read
+ data_hex : Hex string binary data to be written
+ """
+ if not isinstance(self.selected_file, LinFixedEF):
+ raise TypeError("Only works with Linear Fixed EF")
+ 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
+ and then write it to the given record on the card.
+
+ Args:
+ rec_nr : Record number to read
+ data_hex : Abstract data to be written
+ """
+ data_hex = self.selected_file.encode_record_hex(data, rec_nr)
+ return self.update_record(rec_nr, data_hex)
+
+ def retrieve_data(self, tag: int = 0):
+ """Read a DO/TLV as binary data.
+
+ Args:
+ tag : Tag of TLV/DO to read
+ Returns:
+ hex string of full BER-TLV DO including Tag and Length
+ """
+ if not isinstance(self.selected_file, BerTlvEF):
+ raise TypeError("Only works with BER-TLV EF")
+ # returns a string of hex nibbles
+ return self.rs.card._scc.retrieve_data(self.selected_file.fid, tag)
+
+ def retrieve_tags(self):
+ """Retrieve tags available on BER-TLV EF.
+
+ Returns:
+ list of integer tags contained in EF
+ """
+ if not isinstance(self.selected_file, BerTlvEF):
+ raise TypeError("Only works with BER-TLV EF")
+ 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)
+
+ def set_data(self, tag: int, data_hex: str):
+ """Update a TLV/DO with given binary data
+
+ Args:
+ tag : Tag of TLV/DO to be written
+ data_hex : Hex string binary data to be written (value portion)
+ """
+ if not isinstance(self.selected_file, BerTlvEF):
+ raise TypeError("Only works with BER-TLV EF")
+ 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."""
+ if cmd_app and self.selected_file.shell_commands:
+ for c in self.selected_file.shell_commands:
+ cmd_app.unregister_command_set(c)
+
+
+
diff --git a/pySim/sysmocom_sja2.py b/pySim/sysmocom_sja2.py
index 1bd4a8a..bfd0ff5 100644
--- a/pySim/sysmocom_sja2.py
+++ b/pySim/sysmocom_sja2.py
@@ -21,6 +21,7 @@
from struct import pack, unpack
from pySim.utils import *
from pySim.filesystem import *
+from pySim.runtime import RuntimeState
from pySim.ts_102_221 import CardProfileUICC
from pySim.construct import *
from construct import *
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/33702
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I9ec54304dd8f4a4cba9487054a8eb8d265c2d340
Gerrit-Change-Number: 33702
Gerrit-PatchSet: 4
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: merged