laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/36012?usp=email )
Change subject: pylint: esim/saip/validation.py
......................................................................
pylint: esim/saip/validation.py
pySim/esim/saip/validation.py:95:42: C0117: Consider changing "not not ('usim' in m_svcs or 'isim' in m_svcs)" to "'usim' in m_svcs or 'isim' in m_svcs" (unnecessary-negation)
pySim/esim/saip/validation.py:129:0: C0305: Trailing newlines (trailing-newlines)
Change-Id: Idcc9871d6a7068e8aedbd8cd81f4156918af5e50
---
M pySim/esim/saip/validation.py
1 file changed, 13 insertions(+), 2 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/12/36012/1
diff --git a/pySim/esim/saip/validation.py b/pySim/esim/saip/validation.py
index acf1864..f9f5773 100644
--- a/pySim/esim/saip/validation.py
+++ b/pySim/esim/saip/validation.py
@@ -92,7 +92,7 @@
raise ProfileError('get-identity mandatory, but no usim or isim')
if 'profile-a-x25519' in m_svcs and not ('usim' in m_svcs or 'isim' in m_svcs):
raise ProfileError('profile-a-x25519 mandatory, but no usim or isim')
- if 'profile-a-p256' in m_svcs and not not ('usim' in m_svcs or 'isim' in m_svcs):
+ if 'profile-a-p256' in m_svcs and not ('usim' in m_svcs or 'isim' in m_svcs):
raise ProfileError('profile-a-p256 mandatory, but no usim or isim')
FileChoiceList = List[Tuple]
@@ -126,4 +126,3 @@
def check_forbidden(self, l: FileChoiceList):
"""Perform checks for forbidden parameters as described in Section 8.3.3."""
-
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/36012?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Idcc9871d6a7068e8aedbd8cd81f4156918af5e50
Gerrit-Change-Number: 36012
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: newchange
laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/36013?usp=email )
Change subject: saip: improve docstrings
......................................................................
saip: improve docstrings
Change-Id: I0ca82a434e0bde3dc1b304dfc179d568588631c6
---
M pySim/esim/saip/__init__.py
M pySim/esim/saip/personalization.py
2 files changed, 30 insertions(+), 3 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/13/36013/1
diff --git a/pySim/esim/saip/__init__.py b/pySim/esim/saip/__init__.py
index 82ed2b7..b8e41ce 100644
--- a/pySim/esim/saip/__init__.py
+++ b/pySim/esim/saip/__init__.py
@@ -30,7 +30,13 @@
asn1 = compile_asn1_subdir('saip')
class File:
- """Internal representation of a file in a profile filesystem."""
+ """Internal representation of a file in a profile filesystem.
+
+ Parameters:
+ pename: Name string of the profile element
+ l: List of tuples [fileDescriptor, fillFileContent, fillFileOffset profile elements]
+ template: Applicable FileTemplate describing defaults as per SAIP spec
+ """
def __init__(self, pename: str, l: Optional[List[Tuple]] = None, template: Optional[templates.FileTemplate] = None):
self.pe_name = pename
self.template = template
@@ -103,7 +109,7 @@
@staticmethod
def linearize_file_content(l: List[Tuple]) -> Optional[io.BytesIO]:
- """linearize a list of fillFileContent + fillFileOffset tuples."""
+ """linearize a list of fillFileContent / fillFileOffset tuples into a stream of bytes."""
stream = io.BytesIO()
for k, v in l:
if k == 'doNotCreate':
@@ -125,6 +131,7 @@
return "File(%s): %s" % (self.pe_name, self.fileDescriptor)
class ProfileElement:
+ """Class representing a Profile Element (PE) within a SAIP Profile."""
FILE_BEARING = ['mf', 'cd', 'telecom', 'usim', 'opt-usim', 'isim', 'opt-isim', 'phonebook', 'gsm-access',
'csim', 'opt-csim', 'eap', 'df-5gs', 'df-saip', 'df-snpn', 'df-5gprose', 'iot', 'opt-iot']
def _fixup_sqnInit_dec(self) -> None:
@@ -162,6 +169,7 @@
@property
def header_name(self) -> str:
+ """Return the name of the header field within the profile element."""
# unneccessarry compliaction by inconsistent naming :(
if self.type.startswith('opt-'):
return self.type.replace('-','') + '-header'
@@ -169,10 +177,12 @@
@property
def header(self):
+ """Return the decoded ProfileHeader."""
return self.decoded.get(self.header_name, None)
@property
def templateID(self):
+ """Return the decoded templateID used by this profile element (if any)."""
return self.decoded.get('templateID', None)
@property
@@ -216,9 +226,12 @@
self.pes_by_naa: Dict = {}
def get_pes_for_type(self, tname: str) -> List[ProfileElement]:
+ """Return list of profile elements present for given profile element type."""
return self.pe_by_type.get(tname, [])
def get_pe_for_type(self, tname: str) -> Optional[ProfileElement]:
+ """Return a single profile element for given profile element type. Works only for
+ types of which there is only a signle instance in the PE Sequence!"""
l = self.get_pes_for_type(tname)
if len(l) == 0:
return None
diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py
index fd83387..5adaf2e 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -32,7 +32,8 @@
return file
class ClassVarMeta(abc.ABCMeta):
- """Metaclass that puts all additional keyword-args into the class."""
+ """Metaclass that puts all additional keyword-args into the class. We use this to have one
+ class definition for something like a PIN, and then have derived classes for PIN1, PIN2, ..."""
def __new__(metacls, name, bases, namespace, **kwargs):
#print("Meta_new_(metacls=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (metacls, name, bases, namespace, kwargs))
x = super().__new__(metacls, name, bases, namespace)
@@ -78,6 +79,7 @@
return filtered[0]
class Puk(ConfigurableParameter, metaclass=ClassVarMeta):
+ """Configurable PUK (Pin Unblock Code). String ASCII-encoded digits."""
keyReference = None
def apply(self, pes: ProfileElementSequence):
mf_pes = pes.pes_by_naa['mf'][0]
@@ -93,6 +95,7 @@
pass
class Pin(ConfigurableParameter, metaclass=ClassVarMeta):
+ """Configurable PIN (Personal Identification Number). String of digits."""
keyReference = None
def apply(self, pes: ProfileElementSequence):
mf_pes = pes.pes_by_naa['mf'][0]
@@ -105,6 +108,7 @@
return
raise ValueError('cannot find pinCode')
class AppPin(ConfigurableParameter, metaclass=ClassVarMeta):
+ """Configurable PIN (Personal Identification Number). String of digits."""
keyReference = None
def _apply_one(self, pe: ProfileElement):
pinCodes = obtain_first_pe_from_pelist(pe, 'pinCodes')
@@ -134,6 +138,7 @@
class AlgoConfig(ConfigurableParameter, metaclass=ClassVarMeta):
+ """Configurable Algorithm parameter. bytes."""
key = None
def apply(self, pes: ProfileElementSequence):
for pe in pes.get_pes_for_type('akaParameter'):
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/36013?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I0ca82a434e0bde3dc1b304dfc179d568588631c6
Gerrit-Change-Number: 36013
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: newchange
laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/36014?usp=email )
Change subject: saip.personalization: include encode/decode of value; add validation method
......................................................................
saip.personalization: include encode/decode of value; add validation method
Change-Id: Ia9fa39c25817448afb191061acd4be894300eeef
---
M pySim/esim/saip/personalization.py
1 file changed, 81 insertions(+), 13 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/14/36014/1
diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py
index 5adaf2e..fe97e59 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -18,6 +18,7 @@
import abc
from typing import List, Tuple
+from pySim.utils import enc_iccid, enc_imsi, h2b, rpad, sanitize_iccid
from pySim.esim.saip import ProfileElement, ProfileElementSequence
def remove_unwanted_tuples_from_list(l: List[Tuple], unwanted_keys: List[str]) -> List[Tuple]:
@@ -47,26 +48,56 @@
def __init__(self, value):
self.value = value
+ def validate(self):
+ """Optional validation method. Can be used by derived classes to perform validation
+ of the input value (self.value). Will raise an exception if validation fails."""
+ pass
+
@abc.abstractmethod
def apply(self, pes: ProfileElementSequence):
pass
class Iccid(ConfigurableParameter):
- """Configurable ICCID. Expects the value to be in EF.ICCID format."""
+ """Configurable ICCID. Expects the value to be a string of decimal digits.
+ If the string of digits is only 18 digits long, a Luhn check digit will be added."""
name = 'iccid'
+
+ def validate(self):
+ # convert to string as it migt be an integer
+ iccid_str = str(self.value)
+ if len(iccid_str) < 18 or len(iccid_str) > 20:
+ raise ValueError('ICCID must be 18, 19 or 20 digits long')
+ if not iccid_str.isdecimal():
+ raise ValueError('ICCID must only contain decimal digits')
+
def apply(self, pes: ProfileElementSequence):
- # patch the header; FIXME: swap nibbles!
- pes.get_pe_for_type('header').decoded['iccid'] = self.value
+ iccid_str = sanitize_iccid(self.value)
+ # patch the header
+ pes.get_pe_for_type('header').decoded['iccid'] = iccid_str
# patch MF/EF.ICCID
- file_replace_content(pes.get_pe_for_type('mf').decoded['ef-iccid'], bytes(self.value))
+ file_replace_content(pes.get_pe_for_type('mf').decoded['ef-iccid'], h2b(enc_iccid(iccid_str)))
class Imsi(ConfigurableParameter):
- """Configurable IMSI. Expects value to be n EF.IMSI format."""
+ """Configurable IMSI. Expects value to be a string of digits. Automatically sets the ACC to
+ the last digit of the IMSI."""
name = 'imsi'
+
+ def validate(self):
+ # convert to string as it migt be an integer
+ imsi_str = str(self.value)
+ if len(imsi_str < 6) or len(imsi_str) > 15:
+ raise ValueError('IMSI must be 6..15 digits long')
+ if not imsi_str.isdecimal():
+ raise ValueError('IMSI must only contain decimal digits')
+
def apply(self, pes: ProfileElementSequence):
+ imsi_str = str(self.value)
+ # we always use the least significant byte of the IMSI as ACC
+ acc = (1 << int(imsi_str[-1]))
# patch ADF.USIM/EF.IMSI
- for pe in pes.get_pes_by_type('usim'):
- file_replace_content(pe.decoded['ef-imsi'], self.value)
+ for pe in pes.get_pes_for_type('usim'):
+ file_replace_content(pe.decoded['ef-imsi'], h2b(enc_imsi(imsi_str)))
+ file_replace_content(pe.decoded['ef-acc'], acc.to_bytes(2, 'big'))
# TODO: DF.GSM_ACCESS if not linked?
def obtain_singleton_pe_from_pelist(l: List[ProfileElement], wanted_type: str) -> ProfileElement:
@@ -81,12 +112,19 @@
class Puk(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable PUK (Pin Unblock Code). String ASCII-encoded digits."""
keyReference = None
+ def validate(self):
+ # convert to string as it migt be an integer
+ puk_str = str(self.value)
+ # FIXME: valid length?
+ if not puk_str.isdecimal():
+ raise ValueError('PUK must only contain decimal digits')
+
def apply(self, pes: ProfileElementSequence):
mf_pes = pes.pes_by_naa['mf'][0]
pukCodes = obtain_singleton_pe_from_pelist(mf_pes, 'pukCodes')
for pukCode in pukCodes.decoded['pukCodes']:
if pukCode['keyReference'] == self.keyReference:
- pukCode['pukValue'] = self.value
+ pukCode['pukValue'] = h2b(self.value)
return
raise ValueError('cannot find pukCode')
class Puk1(Puk, keyReference=0x01):
@@ -97,29 +135,46 @@
class Pin(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable PIN (Personal Identification Number). String of digits."""
keyReference = None
+ def validate(self):
+ # convert to string as it migt be an integer
+ pin_str = str(self.value)
+ if len(pin_str) < 4 or len(pin_str) > 8:
+ raise ValueError('PIN mus be 4..8 digits long')
+ if not pin_str.isdecimal():
+ raise ValueError('PIN must only contain decimal digits')
def apply(self, pes: ProfileElementSequence):
+ pin = ''.join(['%02x' % (ord(x)) for x in self.value])
+ padded_pin = rpad(pin, 16)
mf_pes = pes.pes_by_naa['mf'][0]
pinCodes = obtain_first_pe_from_pelist(mf_pes, 'pinCodes')
if pinCodes.decoded['pinCodes'][0] != 'pinconfig':
return
for pinCode in pinCodes.decoded['pinCodes'][1]:
if pinCode['keyReference'] == self.keyReference:
- pinCode['pinValue'] = self.value
+ pinCode['pinValue'] = h2b(padded_pin)
return
raise ValueError('cannot find pinCode')
class AppPin(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable PIN (Personal Identification Number). String of digits."""
keyReference = None
+ def validate(self):
+ # convert to string as it migt be an integer
+ pin_str = str(self.value)
+ if len(pin_str) < 4 or len(pin_str) > 8:
+ raise ValueError('PIN mus be 4..8 digits long')
+ if not pin_str.isdecimal():
+ raise ValueError('PIN must only contain decimal digits')
def _apply_one(self, pe: ProfileElement):
+ pin = ''.join(['%02x' % (ord(x)) for x in self.value])
+ padded_pin = rpad(pin, 16)
pinCodes = obtain_first_pe_from_pelist(pe, 'pinCodes')
if pinCodes.decoded['pinCodes'][0] != 'pinconfig':
return
for pinCode in pinCodes.decoded['pinCodes'][1]:
if pinCode['keyReference'] == self.keyReference:
- pinCode['pinValue'] = self.value
+ pinCode['pinValue'] = h2b(padded_pin)
return
raise ValueError('cannot find pinCode')
-
def apply(self, pes: ProfileElementSequence):
for naa in pes.pes_by_naa:
if naa not in ['usim','isim','csim','telecom']:
@@ -140,6 +195,9 @@
class AlgoConfig(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable Algorithm parameter. bytes."""
key = None
+ def validate(self):
+ if not isinstance(self.value, (BytesIO, bytes, bytearray)):
+ raise ValueError('Value must be of bytes-like type')
def apply(self, pes: ProfileElementSequence):
for pe in pes.get_pes_for_type('akaParameter'):
algoConfiguration = pe.decoded['algoConfiguration']
@@ -152,5 +210,6 @@
class Opc(AlgoConfig, key='opc'):
pass
class AlgorithmID(AlgoConfig, key='algorithmID'):
- pass
-
+ def validate(self):
+ if self.value not in [1, 2, 3]:
+ raise ValueError('Invalid algorithmID %s' % (self.value))
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/36014?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Ia9fa39c25817448afb191061acd4be894300eeef
Gerrit-Change-Number: 36014
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: newchange
laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/35952?usp=email )
Change subject: ts_31_102: Add support for "USIM supporting non-IMSI SUPI Type"
......................................................................
ts_31_102: Add support for "USIM supporting non-IMSI SUPI Type"
This type of USIM was introduced in Release 16.4. It is basically
a copy of ADF.USIM without the EF.IMSI file and a dedicated AID.
Change-Id: Ifcde27873a398273a89889bb38537f79859383e9
---
M pySim/ts_31_102.py
1 file changed, 25 insertions(+), 3 deletions(-)
Approvals:
laforge: Looks good to me, approved
Jenkins Builder: Verified
diff --git a/pySim/ts_31_102.py b/pySim/ts_31_102.py
index 0ddf218..2536603 100644
--- a/pySim/ts_31_102.py
+++ b/pySim/ts_31_102.py
@@ -10,7 +10,7 @@
#
# Copyright (C) 2020 Supreeth Herle <herlesupreeth(a)gmail.com>
-# Copyright (C) 2021-2023 Harald Welte <laforge(a)osmocom.org>
+# Copyright (C) 2021-2024 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
@@ -1443,14 +1443,13 @@
class ADF_USIM(CardADF):
def __init__(self, aid='a0000000871002', has_fs=True, name='ADF.USIM', fid=None, sfid=None,
- desc='USIM Application'):
+ desc='USIM Application', has_imsi=True):
super().__init__(aid=aid, has_fs=has_fs, fid=fid, sfid=sfid, name=name, desc=desc)
# add those commands to the general commands of a TransparentEF
self.shell_commands += [self.AddlShellCommands()]
files = [
EF_LI(sfid=0x02),
- EF_IMSI(sfid=0x07),
EF_Keys(),
EF_Keys('6f09', 0x09, 'EF.KeysPS',
desc='Ciphering and Integrity Keys for PS domain'),
@@ -1571,6 +1570,10 @@
DF_5G_ProSe(service=139),
DF_SAIP(),
]
+
+ if has_imsi:
+ files.append(EF_IMSI(sfid=0x07))
+
self.add_files(files)
def decode_select_response(self, data_hex):
@@ -1666,3 +1669,10 @@
class CardApplicationUSIM(CardApplication):
def __init__(self):
super().__init__('USIM', adf=ADF_USIM(), sw=sw_usim)
+
+# TS 31.102 Annex N + TS 102 220 Annex E
+class CardApplicationUSIMnonIMSI(CardApplication):
+ def __init__(self):
+ adf = ADF_USIM(aid='a000000087100b', name='ADF.USIM-non-IMSI', has_imsi=False,
+ desc='3GPP USIM (non-IMSI SUPI Type) - TS 31.102 Annex N')
+ super().__init__('USIM-non-IMSI', adf=adf, sw=sw_usim)
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35952?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Ifcde27873a398273a89889bb38537f79859383e9
Gerrit-Change-Number: 35952
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: merged
laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/35953?usp=email )
Change subject: Add terminal_capability command to send TERMINAL CAPABILITY
......................................................................
Add terminal_capability command to send TERMINAL CAPABILITY
TS 102 221 specifies a TERMINAL CAPABILITY command using which the
terminal (Software + hardware talking to the card) can expose their
capabilities. This is also used in the eUICC universe to let the eUICC
know which features are supported.
Change-Id: Iaeb8b4c34524edbb93217bf401e466399626e9b0
---
M docs/shell.rst
M pySim/ts_102_221.py
2 files changed, 136 insertions(+), 2 deletions(-)
Approvals:
laforge: Looks good to me, approved
Jenkins Builder: Verified
diff --git a/docs/shell.rst b/docs/shell.rst
index d86f500..487d22e 100644
--- a/docs/shell.rst
+++ b/docs/shell.rst
@@ -473,7 +473,18 @@
:module: pySim.ts_102_221
:func: CardProfileUICC.AddlShellCommands.resume_uicc_parser
+terminal_capability
+~~~~~~~~~~~~~~~~~~~
+This command allows you to perform the TERMINAL CAPABILITY command towards the card.
+TS 102 221 specifies the TERMINAL CAPABILITY command using which the
+terminal (Software + hardware talking to the card) can expose their
+capabilities. This is also used in the eUICC universe to let the eUICC
+know which features are supported.
+
+.. argparse::
+ :module: pySim.ts_102_221
+ :func: CardProfileUICC.AddlShellCommands.term_cap_parser
Linear Fixed EF commands
diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py
index 37b7856..1b40b09 100644
--- a/pySim/ts_102_221.py
+++ b/pySim/ts_102_221.py
@@ -1,7 +1,7 @@
# coding=utf-8
"""Utilities / Functions related to ETSI TS 102 221, the core UICC spec.
-(C) 2021 by Harald Welte <laforge(a)osmocom.org>
+(C) 2021-2024 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
@@ -80,6 +80,10 @@
CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']),
])
+
+# ETSI TS 102 221 6.2.1
+SupplyVoltageClasses = FlagsEnum(Int8ub, a=0x1, b=0x2, c=0x4, d=0x8, e=0x10)
+
# ETSI TS 102 221 11.1.1.4.2
class FileSize(BER_TLV_IE, tag=0x80):
_construct = GreedyInteger(minlen=2)
@@ -131,7 +135,7 @@
# ETSI TS 102 221 11.1.1.4.6.2
class ApplicationPowerConsumption(BER_TLV_IE, tag=0x81):
- _construct = Struct('voltage_class'/Int8ub,
+ _construct = Struct('voltage_class'/SupplyVoltageClasses,
'power_consumption_ma'/Int8ub,
'reference_freq_100k'/Int8ub)
@@ -283,6 +287,33 @@
return val
return {d[0]: newval(inmap, d[0], d[1]) for d in indata.items()}
+# TS 102 221 11.1.19.2.1
+class TerminalPowerSupply(BER_TLV_IE, tag=0x80):
+ _construct = Struct('used_supply_voltage_class'/SupplyVoltageClasses,
+ 'maximum_available_power_supply'/Int8ub,
+ 'actual_used_freq_100k'/Int8ub)
+
+# TS 102 221 11.1.19.2.2
+class ExtendedLchanTerminalSupport(BER_TLV_IE, tag=0x81):
+ _construct = GreedyBytes
+
+# TS 102 221 11.1.19.2.3
+class AdditionalInterfacesSupport(BER_TLV_IE, tag=0x82):
+ _construct = FlagsEnum(Int8ub, uicc_clf=0x01)
+
+# TS 102 221 11.1.19.2.4 + SGP.32 v3.0 3.4.2 RSP Device Capabilities
+class AdditionalTermCapEuicc(BER_TLV_IE, tag=0x83):
+ _construct = FlagsEnum(Int8ub, lui_d=0x01, lpd_d=0x02, lds_d=0x04, lui_e_scws=0x08,
+ metadata_update_alerting=0x10,
+ enterprise_capable_device=0x20,
+ lui_e_e4e=0x40,
+ lpr=0x80)
+
+# TS 102 221 11.1.19.2.0
+class TerminalCapability(BER_TLV_IE, tag=0xa9, nested=[TerminalPowerSupply, ExtendedLchanTerminalSupport,
+ AdditionalInterfacesSupport, AdditionalTermCapEuicc]):
+ pass
+
# ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
class _AM_DO_DF(DataObject):
def __init__(self):
@@ -901,3 +932,81 @@
of the card is required between SUSPEND and RESUME, and only very few non-RESUME
commands are permitted between SUSPEND and RESUME. See TS 102 221 Section 11.1.22."""
self._cmd.card._scc.resume_uicc(opts.token)
+
+ term_cap_parser = argparse.ArgumentParser()
+ # power group
+ tc_power_grp = term_cap_parser.add_argument_group('Terminal Power Supply')
+ tc_power_grp.add_argument('--used-supply-voltage-class', type=str, choices=['a','b','c','d','e'],
+ help='Actual used Supply voltage class')
+ tc_power_grp.add_argument('--maximum-available-power-supply', type=auto_uint8,
+ help='Maximum available power supply of the terminal')
+ tc_power_grp.add_argument('--actual-used-freq-100k', type=auto_uint8,
+ help='Actual used clock frequency (in units of 100kHz)')
+ # no separate groups for those two
+ tc_elc_grp = term_cap_parser.add_argument_group('Extended logical channels terminal support')
+ tc_elc_grp.add_argument('--extended-logical-channel', action='store_true',
+ help='Extended Logical Channel supported')
+ tc_aif_grp = term_cap_parser.add_argument_group('Additional interfaces support')
+ tc_aif_grp.add_argument('--uicc-clf', action='store_true',
+ help='Local User Interface in the Device (LUId) supported')
+ # eUICC group
+ tc_euicc_grp = term_cap_parser.add_argument_group('Additional Terminal capability indications related to eUICC')
+ tc_euicc_grp.add_argument('--lui-d', action='store_true',
+ help='Local User Interface in the Device (LUId) supported')
+ tc_euicc_grp.add_argument('--lpd-d', action='store_true',
+ help='Local Profile Download in the Device (LPDd) supported')
+ tc_euicc_grp.add_argument('--lds-d', action='store_true',
+ help='Local Discovery Service in the Device (LPDd) supported')
+ tc_euicc_grp.add_argument('--lui-e-scws', action='store_true',
+ help='LUIe based on SCWS supported')
+ tc_euicc_grp.add_argument('--metadata-update-alerting', action='store_true',
+ help='Metadata update alerting supported')
+ tc_euicc_grp.add_argument('--enterprise-capable-device', action='store_true',
+ help='Enterprise Capable Device')
+ tc_euicc_grp.add_argument('--lui-e-e4e', action='store_true',
+ help='LUIe using E4E (ENVELOPE tag E4) supported')
+ tc_euicc_grp.add_argument('--lpr', action='store_true',
+ help='LPR (LPA Proxy) supported')
+
+ @cmd2.with_argparser(term_cap_parser)
+ def do_terminal_capability(self, opts):
+ """Perform the TERMINAL CAPABILITY function. Used to inform the UICC about terminal capability."""
+ ps_flags = {}
+ addl_if_flags = {}
+ euicc_flags = {}
+
+ opts_dict = vars(opts)
+
+ power_items = ['used_supply_voltage_class', 'maximum_available_power_supply', 'actual_used_freq_100k']
+ if any(opts_dict[x] for x in power_items):
+ if not all(opts_dict[x] for x in power_items):
+ raise argparse.ArgumentTypeError('If any of the Terminal Power Supply group options are used, all must be specified')
+
+ for k, v in opts_dict.items():
+ if k in AdditionalInterfacesSupport._construct.flags.keys():
+ addl_if_flags[k] = v
+ elif k in AdditionalTermCapEuicc._construct.flags.keys():
+ euicc_flags[k] = v
+ elif k in [f.name for f in TerminalPowerSupply._construct.subcons]:
+ if k == 'used_supply_voltage_class' and v:
+ v = {v: True}
+ ps_flags[k] = v
+
+ child_list = []
+ if any(x for x in ps_flags.values()):
+ child_list.append(TerminalPowerSupply(decoded=ps_flags))
+
+ if opts.extended_logical_channel:
+ child_list.append(ExtendedLchanTerminalSupport())
+ if any(x for x in addl_if_flags.values()):
+ child_list.append(AdditionalInterfacesSupport(decoded=addl_if_flags))
+ if any(x for x in euicc_flags.values()):
+ child_list.append(AdditionalTermCapEuicc(decoded=euicc_flags))
+
+ print(child_list)
+ tc = TerminalCapability(children=child_list)
+ self.terminal_capability(b2h(tc.to_tlv()))
+
+ def terminal_capability(self, data:Hexstr):
+ cmd_hex = "80AA0000%02x%s" % (len(data)//2, data)
+ _rsp_hex, _sw = self._cmd.lchan.scc.send_apdu_checksw(cmd_hex)
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35953?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Iaeb8b4c34524edbb93217bf401e466399626e9b0
Gerrit-Change-Number: 35953
Gerrit-PatchSet: 3
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-MessageType: merged
laforge has posted comments on this change. ( https://gerrit.osmocom.org/c/pysim/+/35995?usp=email )
Change subject: Dynamically determine maximum CMD data length depending on SCP
......................................................................
Patch Set 1: Code-Review+2
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35995?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I0a081a23efe20c77557600e62b52ba90a401058d
Gerrit-Change-Number: 35995
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Comment-Date: Sun, 18 Feb 2024 18:30:55 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
Gerrit-MessageType: comment