mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-05-02 14:42:49 +03:00
personalization: make AlgorithmID a new EnumParam
The AlgorithmID has a few preset values, and hardly anyone knows which is which. So instead of entering '1', '2' or '3', make it work with prededined values 'Milenage', 'TUAK' and 'usim-test'. Implement the enum value part abstractly in new EnumParam. Make AlgorithmID a subclass of EnumParam and define the values as from pySim/esim/asn1/saip/PE_Definitions-3.3.1.asn Related: SYS#6768 Change-Id: I71c2ec1b753c66cb577436944634f32792353240 Jenkins: skip-card-test
This commit is contained in:
committed by
Vadim Yanitskiy
parent
d7072e9263
commit
5f1c7d603c
@@ -16,7 +16,9 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import enum
|
||||||
import io
|
import io
|
||||||
|
import re
|
||||||
from typing import List, Tuple, Generator, Optional
|
from typing import List, Tuple, Generator, Optional
|
||||||
|
|
||||||
from osmocom.tlv import camel_to_snake
|
from osmocom.tlv import camel_to_snake
|
||||||
@@ -352,6 +354,72 @@ class BinaryParam(ConfigurableParameter):
|
|||||||
return bytes(val)
|
return bytes(val)
|
||||||
|
|
||||||
|
|
||||||
|
class EnumParam(ConfigurableParameter):
|
||||||
|
"""ConfigurableParameter for named integer enumeration values.
|
||||||
|
|
||||||
|
Subclasses must define a nested enum.IntEnum named 'Values' listing all valid names and their
|
||||||
|
integer codes. apply_val() and get_values_from_pes() are not implemented here and this must
|
||||||
|
be inherited from another mixin."""
|
||||||
|
|
||||||
|
class Values(enum.IntEnum):
|
||||||
|
pass # subclasses override this
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate_val(cls, val) -> int:
|
||||||
|
if isinstance(val, int):
|
||||||
|
try:
|
||||||
|
return int(cls.Values(val))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
elif isinstance(val, str):
|
||||||
|
member = cls.map_name_to_val(val, strict=False)
|
||||||
|
if member is not None:
|
||||||
|
return member
|
||||||
|
|
||||||
|
valid = ', '.join(m.name for m in cls.Values)
|
||||||
|
raise ValueError(f"{cls.get_name()}: invalid argument: {val!r}. Valid arguments are: {valid}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def map_name_to_val(cls, name: str, strict=True) -> int:
|
||||||
|
"""Return the integer value for a given enum member name. Performs an exact match first,
|
||||||
|
then falls back to fuzzy matching (case-insensitive, punctuation-insensitive)."""
|
||||||
|
try:
|
||||||
|
return int(cls.Values[name])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
clean = cls.clean_name_str(name)
|
||||||
|
for member in cls.Values:
|
||||||
|
if cls.clean_name_str(member.name) == clean:
|
||||||
|
return int(member)
|
||||||
|
|
||||||
|
if strict:
|
||||||
|
valid = ', '.join(m.name for m in cls.Values)
|
||||||
|
raise ValueError(f"{cls.get_name()}: {name!r} is not a known value. Known values are: {valid}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def map_val_to_name(cls, val, strict=False) -> str:
|
||||||
|
"""Return the enum member name for a given integer value."""
|
||||||
|
try:
|
||||||
|
return cls.Values(val).name
|
||||||
|
except ValueError:
|
||||||
|
if strict:
|
||||||
|
raise ValueError(f"{cls.get_name()}: {val!r} ({type(val).__name__}) is not a known value.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def name_normalize(cls, name: str) -> str:
|
||||||
|
"""Map a (possibly fuzzy) name to its canonical enum member name."""
|
||||||
|
return cls.Values(cls.map_name_to_val(name)).name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clean_name_str(cls, val: str) -> str:
|
||||||
|
"""Strip punctuation and case for fuzzy name comparison.
|
||||||
|
Treats hyphens and underscores as equivalent (both removed)."""
|
||||||
|
return re.sub('[^0-9A-Za-z]', '', val).lower()
|
||||||
|
|
||||||
|
|
||||||
class Iccid(DecimalParam):
|
class Iccid(DecimalParam):
|
||||||
"""ICCID Parameter. Input: string of decimal digits.
|
"""ICCID Parameter. Input: string of decimal digits.
|
||||||
If the string of digits is only 18 digits long, add a Luhn check digit."""
|
If the string of digits is only 18 digits long, add a Luhn check digit."""
|
||||||
@@ -775,21 +843,34 @@ class AlgoConfig(ConfigurableParameter):
|
|||||||
# if it is an int (algorithmID), just pass thru as int
|
# if it is an int (algorithmID), just pass thru as int
|
||||||
yield { cls.name: val }
|
yield { cls.name: val }
|
||||||
|
|
||||||
|
class AlgorithmID(EnumParam, AlgoConfig):
|
||||||
class AlgorithmID(DecimalParam, AlgoConfig):
|
"""use validate_val() from EnumParam, and apply_val() from AlgoConfig.
|
||||||
|
In get_values_from_pes(), return enum value names, not raw values."""
|
||||||
|
name = "Algorithm"
|
||||||
algo_config_key = 'algorithmID'
|
algo_config_key = 'algorithmID'
|
||||||
allow_len = 1
|
example_input = "Milenage"
|
||||||
example_input = 1 # Milenage
|
|
||||||
default_source = param_source.ConstantSource
|
default_source = param_source.ConstantSource
|
||||||
|
|
||||||
|
# as in pySim/esim/asn1/saip/PE_Definitions-3.3.1.asn
|
||||||
|
class Values(enum.IntEnum):
|
||||||
|
Milenage = 1
|
||||||
|
TUAK = 2
|
||||||
|
usim_test = 3 # input 'usim-test' also accepted via fuzzy matching
|
||||||
|
|
||||||
|
# EnumParam.validate_val() returns the int values from Values
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_val(cls, val):
|
def get_values_from_pes(cls, pes: ProfileElementSequence):
|
||||||
val = super().validate_val(val)
|
# return enum names, not raw values.
|
||||||
val = int(val)
|
# use of super(): this intends to call AlgoConfig.get_values_from_pes() so that the cls argument is this cls
|
||||||
valid = (1, 2, 3)
|
# here (AlgorithmID); i.e. AlgoConfig.get_values_from_pes(pes) doesn't work, because AlgoConfig needs to look up
|
||||||
if val not in valid:
|
# cls.algo_config_key.
|
||||||
raise ValueError(f'Invalid algorithmID {val!r}, must be one of {valid}')
|
for d in super(cls, cls).get_values_from_pes(pes):
|
||||||
return val
|
if cls.name in d:
|
||||||
|
# convert int to value string
|
||||||
|
val = d[cls.name]
|
||||||
|
d[cls.name] = cls.map_val_to_name(val, strict=True)
|
||||||
|
yield d
|
||||||
|
|
||||||
class K(BinaryParam, AlgoConfig):
|
class K(BinaryParam, AlgoConfig):
|
||||||
"""use validate_val() from BinaryParam, and apply_val() from AlgoConfig"""
|
"""use validate_val() from BinaryParam, and apply_val() from AlgoConfig"""
|
||||||
|
|||||||
Reference in New Issue
Block a user