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
This commit is contained in:
Harald Welte 2023-07-11 17:26:39 +02:00
parent f9e2df1296
commit 323a35043f
8 changed files with 105 additions and 15 deletions

View File

@ -137,11 +137,6 @@ def init_card(sl):
# 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

View File

@ -11,7 +11,7 @@ from pySim.filesystem import RuntimeState
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 DummySimLink(LinkBase):
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())

View File

@ -22,7 +22,7 @@ import enum
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 @@ class CardProfileRUIM(CardProfile):
@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)

View File

@ -1307,6 +1307,16 @@ class RuntimeState:
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:

View File

@ -35,8 +35,8 @@ from construct import Optional as COptional
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 @@ class DF_EIRENE(CardDF):
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)

View File

@ -88,6 +88,7 @@ class CardProfile:
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 @@ class CardProfile:
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 @@ class CardProfile:
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

View File

@ -31,7 +31,9 @@ import pySim.iso7816_4 as iso7816_4
# 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 @@ class CardProfileUICC(CardProfile):
# FIXME: DF.CD
EF_UMPC(),
]
addons = [
AddonSIM,
AddonGSMR,
AddonRUIM,
]
sw = {
'Normal': {
'9000': 'Normal ending of the command',
@ -839,7 +846,7 @@ class CardProfileUICC(CardProfile):
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 @@ class CardProfileUICCSIM(CardProfileUICC):
@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

View File

@ -30,9 +30,10 @@ order to describe the files specified in the relevant ETSI + 3GPP specifications
#
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 @@ class CardProfileSIM(CardProfile):
},
}
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 @@ class CardProfileSIM(CardProfile):
@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)