neels has uploaded this change for review.

View Change

personalization: discover all useful ConfigurableParameter subclasses

Discover all non-abstract subclasses of ConfigurableParameter in
ConfigurableParameter.get_all_implementations().

To be able to automatically discover all practically useful
ConfigurableParameter subclasses, introduce the is_abstract flag.
ConfigurableParameter itself sets is_abstract = True, by default
"hiding" subclasses. As soon as a subclass sets is_abstract = False, it
becomes "public". It depends on the calling code to actually implement
that decision -- this flag enables calling code to do so sanely.

For example, the BinaryParam superclass keeps is_abstract = True,
because per se it isn't capable of doing anything. The fully useful K
and Opc subclasses set is_abstract = False.

Implementation choice: I first tried to query an implicit abstract
status via abc / @abstractmethod ways, but it did not match well. A
subclass has no good implicit indicator, we need a flag instead. For
example, a superclass may provide an apply_val() implementation and
hence appear as non-abstract, but it is still not usable because a
specific 'key' member is still None, which various subclasses set.

Related: SYS#6768
Change-Id: I4970657294130b6b65d50ff19ffbb9ebab3be609
---
M pySim/esim/saip/personalization.py
M pySim/utils.py
2 files changed, 30 insertions(+), 1 deletion(-)

git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/95/40095/1
diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py
index bb6eec3..26fcbf7 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -20,7 +20,7 @@
from typing import List, Tuple

from osmocom.tlv import camel_to_snake
-from pySim.utils import enc_iccid, enc_imsi, h2b, rpad, sanitize_iccid
+from pySim.utils import enc_iccid, enc_imsi, h2b, rpad, sanitize_iccid, all_subclasses_of
from pySim.esim.saip import ProfileElement, ProfileElementSequence

def remove_unwanted_tuples_from_list(l: List[Tuple], unwanted_keys: List[str]) -> List[Tuple]:
@@ -93,10 +93,14 @@
changed_der = pes.to_der()
"""

+ # for get_all_implementations(), telling callers about all practically useful parameters
+ is_abstract = True
+
# A subclass can set an explicit string as name (like name = "PIN1").
# If name is left None, then __init__() will set self.name to a name derived from the python class name (like
# "pin1"). See also the get_name() classmethod when you have no instance at hand.
name = None
+
allow_types = (str, int, )
allow_chars = None
strip_chars = None
@@ -209,6 +213,15 @@
return (None, None)
return (min(vals), max(vals))

+ @classmethod
+ def get_all_implementations(cls, blacklist=None, allow_abstract=False):
+ # return a set() so that multiple inheritance does not return dups
+ return set(c
+ for c in all_subclasses_of(cls)
+ if ((allow_abstract or not c.is_abstract)
+ and ((not blacklist) or (c not in blacklist)))
+ )
+

class DecimalParam(ConfigurableParameter):
"""Decimal digits. The input value may be a string of decimal digits like '012345', or an int. The output of
@@ -273,6 +286,7 @@
class Iccid(DecimalParam):
"""ICCID Parameter. Input: string of decimal digits.
If the string of digits is only 18 digits long, add a Luhn check digit."""
+ is_abstract = False
name = 'ICCID'
min_len = 18
max_len = 20
@@ -293,6 +307,7 @@
class Imsi(DecimalParam):
"""Configurable IMSI. Expects value to be a string of digits. Automatically sets the ACC to
the last digit of the IMSI."""
+ is_abstract = False

name = 'IMSI'
min_len = 6
@@ -480,10 +495,12 @@
f" cannot find pukCode with keyReference={cls.keyReference}")

class Puk1(Puk):
+ is_abstract = False
name = 'PUK1'
keyReference = 0x01

class Puk2(Puk):
+ is_abstract = False
name = 'PUK2'
keyReference = 0x81

@@ -515,11 +532,13 @@
+ f' {cls.get_name()} cannot find pinCode with keyReference={cls.keyReference}')

class Pin1(Pin):
+ is_abstract = False
name = 'PIN1'
default_value = '0' * 4 # PIN are usually 4 digits
keyReference = 0x01

class Pin2(Pin1):
+ is_abstract = False
name = 'PIN2'
keyReference = 0x81

@@ -536,10 +555,12 @@
+ f' {cls.get_name()} cannot find pinCode with keyReference={cls.keyReference} in {naa=}')

class Adm1(Pin):
+ is_abstract = False
name = 'ADM1'
keyReference = 0x0A

class Adm2(Adm1):
+ is_abstract = False
name = 'ADM2'
keyReference = 0x0B

@@ -561,6 +582,7 @@


class AlgorithmID(DecimalParam, AlgoConfig):
+ is_abstract = False
algo_config_key = 'algorithmID'
allow_len = 1
default_value = 1 # Milenage
@@ -577,6 +599,7 @@

class K(BinaryParam, AlgoConfig):
"""use validate_val() from BinaryParam, and apply_val() from AlgoConfig"""
+ is_abstract = False
name = 'K'
algo_config_key = 'key'
allow_len = int(128/8) # length in bytes (from BinaryParam)
diff --git a/pySim/utils.py b/pySim/utils.py
index 48a9998..ec519a7 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -980,3 +980,9 @@
if cla and not cmd.match_cla(cla):
return None
return cmd
+
+
+def all_subclasses_of(cls):
+ for subc in cls.__subclasses__():
+ yield subc
+ yield from all_subclasses_of(subc)

To view, visit change 40095. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I4970657294130b6b65d50ff19ffbb9ebab3be609
Gerrit-Change-Number: 40095
Gerrit-PatchSet: 1
Gerrit-Owner: neels <nhofmeyr@sysmocom.de>