laforge has submitted this change. ( 
https://gerrit.osmocom.org/c/pysim/+/39745?usp=email )

Change subject: personalization: refactor AlgorithmID, K, Opc
......................................................................

personalization: refactor AlgorithmID, K, Opc

Refactor AlgorithmID, K, Opc to the new ConfigurableParameter
implementation style.

K and Opc use a common abstract BinaryParam.

Note from the future: AlgorithmID so far takes "raw" int values, but
will turn to be an "enum" parameter with predefined meaningful strings
in I71c2ec1b753c66cb577436944634f32792353240

Change-Id: I6296fdcfd5d2ed313c4aade57ff43cc362375848
---
M pySim/esim/saip/personalization.py
1 file changed, 98 insertions(+), 40 deletions(-)

Approvals:
  Jenkins Builder: Verified
  laforge: Looks good to me, approved
  neels: Looks good to me, but someone else must approve




diff --git a/pySim/esim/saip/personalization.py 
b/pySim/esim/saip/personalization.py
index d1998dc..c715118 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -254,6 +254,51 @@
         # a DecimalHexParam subclass expects the apply_val() input to be a 
bytes instance ready for the pes
         return h2b(val)

+class IntegerParam(ConfigurableParameter):
+    allow_types = (str, int)
+    allow_chars = '0123456789'
+
+    # two integers, if the resulting int should be range limited
+    min_val = None
+    max_val = None
+
+    @classmethod
+    def validate_val(cls, val):
+        val = super().validate_val(val)
+        val = int(val)
+        exceeds_limits = False
+        if cls.min_val is not None:
+            if val < cls.min_val:
+                exceeds_limits = True
+        if cls.max_val is not None:
+            if val > cls.max_val:
+                exceeds_limits = True
+        if exceeds_limits:
+            raise ValueError(f'Value {val} is out of range, must be 
[{cls.min_val}..{cls.max_val}]')
+        return val
+
+class BinaryParam(ConfigurableParameter):
+    allow_types = (str, io.BytesIO, bytes, bytearray)
+    allow_chars = '0123456789abcdefABCDEF'
+    strip_chars = ' \t\r\n'
+
+    @classmethod
+    def validate_val(cls, val):
+        # take care that min_len and max_len are applied to the binary length 
by converting to bytes first
+        if isinstance(val, str):
+            if cls.strip_chars is not None:
+                val = ''.join(c for c in val if c not in cls.strip_chars)
+            if len(val) & 1:
+                raise ValueError('Invalid hexadecimal string, must have even 
number of digits:'
+                                 f' {val!r} {len(val)=}')
+            try:
+                val = h2b(val)
+            except ValueError as e:
+                raise ValueError(f'Invalid hexadecimal string: {val!r} 
{len(val)=}') from e
+
+        val = super().validate_val(val)
+        return bytes(val)
+

 class Iccid(DecimalParam):
     """ICCID Parameter. Input: string of decimal digits.
@@ -513,42 +558,60 @@
 class Adm2(Pin):
     keyReference = 0x0B

+class AlgoConfig(ConfigurableParameter):
+    algo_config_key = None

-class AlgoConfig(ConfigurableParameter, metaclass=ClassVarMeta):
-    """Configurable Algorithm parameter."""
-    key = None
-    def validate(self):
-        if not isinstance(self.input_value, (io.BytesIO, bytes, bytearray)):
-            raise ValueError('Value must be of bytes-like type')
-        self.value = self.input_value
-    def apply(self, pes: ProfileElementSequence):
+    @classmethod
+    def apply_val(cls, pes: ProfileElementSequence, val):
+        found = 0
         for pe in pes.get_pes_for_type('akaParameter'):
             algoConfiguration = pe.decoded['algoConfiguration']
             if algoConfiguration[0] != 'algoParameter':
                 continue
-            algoConfiguration[1][self.key] = self.value
+            algoConfiguration[1][cls.algo_config_key] = val
+            found += 1
+        if not found:
+            raise ValueError('input template UPP has unexpected structure:'
+                             f' {cls.__name__} cannot find algoParameter with 
key={cls.algo_config_key}')

-class K(AlgoConfig, key='key'):
-    pass
-class Opc(AlgoConfig, key='opc'):
-    pass
-class AlgorithmID(AlgoConfig, key='algorithmID'):
-    def validate(self):
-        if self.input_value not in [1, 2, 3]:
-            raise ValueError('Invalid algorithmID %s' % (self.input_value))
-        self.value = self.input_value
-class MilenageRotationConstants(AlgoConfig, key='rotationConstants'):
+class AlgorithmID(DecimalParam, AlgoConfig):
+    algo_config_key = 'algorithmID'
+    allow_len = 1
+
+    @classmethod
+    def validate_val(cls, val):
+        val = super().validate_val(val)
+        val = int(val)
+        valid = (1, 2, 3)
+        if val not in valid:
+            raise ValueError(f'Invalid algorithmID {val!r}, must be one of 
{valid}')
+        return val
+
+class K(BinaryParam, AlgoConfig):
+    """use validate_val() from BinaryParam, and apply_val() from AlgoConfig"""
+    algo_config_key = 'key'
+    allow_len = (128 // 8, 256 // 8) # length in bytes (from BinaryParam); 
TUAK also allows 256 bit
+
+class Opc(K):
+    algo_config_key = 'opc'
+
+class MilenageRotationConstants(BinaryParam, AlgoConfig):
     """rotation constants r1,r2,r3,r4,r5 of Milenage, Range 0..127. See 3GPP 
TS 35.206 Sections 2.3 + 5.3.
     Provided as octet-string concatenation of all 5 constants.  Expects a 
bytes-like object of length 5, with
     each byte in the range of 0..127.  The default value by 3GPP is 
'4000204060' (hex notation)"""
-    def validate(self):
-        super().validate()
-        if len(self.input_value) != 5:
-            raise ValueError('Length of value must be 5 octets')
-        for r in self.input_value:
-            if r > 127:
-                raise ValueError('r values must be between 0 and 127')
-class MilenageXoringConstants(AlgoConfig, key='xoringConstants'):
+    algo_config_key = 'rotationConstants'
+    allow_len = 5 # length in bytes (from BinaryParam)
+
+    @classmethod
+    def validate_val(cls, val):
+        "allow_len checks the length, this in addition checks the value range"
+        val = super().validate_val(val)
+        assert isinstance(val, bytes)
+        if any(r > 127 for r in val):
+            raise ValueError('r values must be in the range 0..127')
+        return val
+
+class MilenageXoringConstants(BinaryParam, AlgoConfig):
     """XOR-ing constants c1,c2,c3,c4,c5 of Milenage, 128bit each. See 3GPP TS 
35.206 Sections 2.3 + 5.3.
     Provided as octet-string concatenation of all 5 constants. The default 
value by 3GPP is the concetenation
     of:
@@ -558,16 +621,11 @@
      00000000000000000000000000000004
      00000000000000000000000000000008
     """
-    def validate(self):
-        super().validate()
-        if len(self.input_value) != 80:
-            raise ValueError('Length of value must be 80 octets')
-class TuakNumberOfKeccak(AlgoConfig, key='numberOfKeccak'):
-    """Number of iterations of Keccak-f[1600] permutation as recomended by 
Section 7.2 of 3GPP TS 35.231.
-    The default value by 3GPP is 1."""
-    def validate(self):
-        if not isinstance(self.input_value, int):
-            raise ValueError('Value must be an integer')
-        if self.input_value < 1 or self.input_value > 255:
-            raise ValueError('Value must be an integer between 1 and 255')
-        self.value = self.input_value
+    algo_config_key = 'xoringConstants'
+    allow_len = 80 # length in bytes (from BinaryParam)
+
+class TuakNumberOfKeccak(IntegerParam, AlgoConfig):
+    """Number of iterations of Keccak-f[1600] permutation as recomended by 
Section 7.2 of 3GPP TS 35.231"""
+    algo_config_key = 'numberOfKeccak'
+    min_val = 1
+    max_val = 255

--
To view, visit https://gerrit.osmocom.org/c/pysim/+/39745?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I6296fdcfd5d2ed313c4aade57ff43cc362375848
Gerrit-Change-Number: 39745
Gerrit-PatchSet: 15
Gerrit-Owner: neels <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: neels <[email protected]>

Reply via email to