Attention is currently required from: neels.
laforge has posted comments on this change by neels. ( https://gerrit.osmocom.org/c/pysim/+/40094?usp=email )
Change subject: personalization: set default values
......................................................................
Patch Set 2: Code-Review-1
(7 comments)
Commit Message:
https://gerrit.osmocom.org/c/pysim/+/40094/comment/02406a05_f38afaab?usp=em… :
PS2, Line 11: This is useful for user interaction, to prefill an input field that
: indicates a valid input to modify to taste.
I beg to differ. Filling default values is dangerous as it makes the form validate without the user having provided reasonable input to all of them.
In other words, having default values is likely to make it easy for users to personalize profiles with something they don't want?
It might depend on the actual parameter, but for sure something like IMSI or K/OPc should never have a default, as there is no reasonable default that is true for most use cases. Those are always individual...
File pySim/esim/saip/personalization.py:
https://gerrit.osmocom.org/c/pysim/+/40094/comment/dc3446be_48aa8fe5?usp=em… :
PS2, Line 278: default_value
there's no point of an all-zero default ICCID.
https://gerrit.osmocom.org/c/pysim/+/40094/comment/36215e1a_5cff6661?usp=em… :
PS2, Line 299:
I also think there's no point in having a default for the IMSI
https://gerrit.osmocom.org/c/pysim/+/40094/comment/decfdbb0_d7ea037e?usp=em… :
PS2, Line 467: default_value = '0' * allow_len
I'm not sure why any security key / pin should have any default value at all. This seems more like its introducing a security issue as anyone not explicitly setting a PUK would get 00000000 instead of a warning/error/exception?
https://gerrit.osmocom.org/c/pysim/+/40094/comment/d1fe8be5_64b46f16?usp=em… :
PS2, Line 494: default_value = '0' * max_len
same here
https://gerrit.osmocom.org/c/pysim/+/40094/comment/1382f00c_548564dc?usp=em… :
PS2, Line 565: default_value = 1 # Milenage
here I agree that a default actually does make sense.
https://gerrit.osmocom.org/c/pysim/+/40094/comment/16919990_827bb8c0?usp=em… :
PS2, Line 582: default_value = '00' * allow_len
no cryptographic key should have any default value. It's just creating security nightmares.
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/40094?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I2672fedcbc32cb7a6cb0c233a4a22112bd9aae03
Gerrit-Change-Number: 40094
Gerrit-PatchSet: 2
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Attention: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Comment-Date: Tue, 22 Apr 2025 08:41:26 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: Yes
Attention is currently required from: laforge.
kirr has posted comments on this change by kirr. ( https://gerrit.osmocom.org/c/osmocom-bb/+/40055?usp=email )
Change subject: trx_toolkit/udp_link: Optimize UDPLink.send
......................................................................
Patch Set 2:
(1 comment)
Patchset:
PS2:
> just a random comment: As we do a lot of io_uring in libosmocore and programs using it in recent tim […]
Harald, thanks for feedback.
Current epoll + send/recv seems to be enough for BTS + 1-2-3 Ms, but if we are to go with more Ms it might indeed be better to optimize more. In my experience most of the overhead was in python-related wrapping, but once that is removed it indeed the IO syscalls that will become the bottleneck. We do not forward a lot of data, as GSM bursts are small, but we do forward a lot of packets, as each GSM burst is being sent via 1 UDP message and to keep the forarding latency minimal we cannot coalesce those messages even for different slots in the same GSM frame. So using io_uring might indeed make sense at some point.
When/if we are to do that I would suggest to cosider using libosmocore directly instead of going through additional third-party library. It should hopefully easy to do that from Cython.
Kirill
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/40055?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I83204545066a925dadbcd0b72cbbc2e3407129fe
Gerrit-Change-Number: 40055
Gerrit-PatchSet: 2
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Comment-Date: Sun, 20 Apr 2025 13:16:32 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: laforge <laforge(a)osmocom.org>
Attention is currently required from: laforge, neels.
fixeria has posted comments on this change by neels. ( https://gerrit.osmocom.org/c/pysim/+/39741?usp=email )
Change subject: [1/6] personalization: refactor: drop ClassVarMeta use
......................................................................
Patch Set 9:
(1 comment)
Patchset:
PS6:
I find it funny that you're saying "normal python" / "plain standard python", as if this was some kind of dialect or a language on top of Python. But the metaclasses are part of the language. PEP 3115 (https://peps.python.org/pep-3115/) dates back to 07-Mar-2007.
As I commented earlier:
> IMO, the old approach is much more compact and thus more readable.
so I would vote against this kind of refactoring, sorry.
> i don't understand what purpose a meta class serves.
In this particular case it allows assigning class attributes in a compact way.
> * fixeria's example shows that the shortest way to achieve class attributes is
You say it's shorter, but looking at this patch I see the modified classes taking nearly twice more lines than the old code. My example simply shows a metaclass `MetaFoo` implementation, that allows to assign class attributes in both ways. But you don't seem to like the idea of mixing up different styles.
> * upcoming patches add a lot of class attributes, and i would like to consistently use one way. I chose the "normal python" way, so as a first step, this patch drops the meta class.
Consistency does sound like a solid argument, ok. And indeed, the approach of using metaclasses becomes less readable when you have a lot of attributes. But this can be avoided by creating intermediate classes (like you already do). And by having less attributes, e.g. `len = (min, max)` instead of `min_len` + `max_len`.
Not willing to block you, just sharing my view. Leaving the final decision up to @laforge@osmocom.org.
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/39741?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I60ea8fd11fb438ec90ddb08b17b658cbb789c051
Gerrit-Change-Number: 39741
Gerrit-PatchSet: 9
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: laforge <laforge(a)osmocom.org>
Gerrit-Attention: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Comment-Date: Sun, 20 Apr 2025 08:06:01 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: neels <nhofmeyr(a)sysmocom.de>
Comment-In-Reply-To: laforge <laforge(a)osmocom.org>
Comment-In-Reply-To: fixeria <vyanitskiy(a)sysmocom.de>
Attention is currently required from: dexter, fixeria, laforge.
Hello Jenkins Builder, dexter, laforge,
I'd like you to reexamine a change. Please visit
https://gerrit.osmocom.org/c/pysim/+/39742?usp=email
to look at the new patch set (#9).
The following approvals got outdated and were removed:
Verified+1 by Jenkins Builder
Change subject: [2/6] personalization: refactor ConfigurableParameter, Iccid, Imsi
......................................................................
[2/6] personalization: refactor ConfigurableParameter, Iccid, Imsi
Main points/rationales of the refactoring, details below:
1) common validation implementation
2) offer classmethods
The new features are optional, and will be heavily used by batch
personalization patches coming soon.
Implement Iccid and Imsi to use the new way, with a common abstract
DecimalParam implementation.
So far leave the other parameter classes working as they always did, to
follow suit in subsequent commits.
Details:
1) common validation implementation:
There are very common validation steps in the various parameter
implementations. It is more convenient and much more readable to
implement those once and set simple validation parameters per subclass.
So there now is a validate_val() classmethod, which subclasses can use
as-is to apply the validation parameters -- or subclasses can override
their cls.validate_val() for specialized validation.
(Those subclasses that this patch doesn't touch still override the
self.validate() instance method. Hence they still work as before this
patch, but don't use the new common features yet.)
2) offer stateless classmethods:
It is useful for...
- batch processing of multiple profiles (in upcoming patches) and
- user input validation
to be able to have classmethods that do what self.validate() and
self.apply() do, but do not modify any self.* members.
So far the paradigm was to create a class instance to keep state about
the value. This remains available, but in addition we make available the
paradigm of a singleton that is stateless (the classmethods).
Using self.validate() and self.apply() still work the same as before
this patch, i.e. via self.input_value and self.value -- but in addition,
there are now classmethods that don't touch self.* members.
Related: SYS#6768
Change-Id: I6522be4c463e34897ca9bff2309b3706a88b3ce8
---
M pySim/esim/saip/personalization.py
1 file changed, 179 insertions(+), 34 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/42/39742/9
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/39742?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I6522be4c463e34897ca9bff2309b3706a88b3ce8
Gerrit-Change-Number: 39742
Gerrit-PatchSet: 9
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
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-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Attention: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Attention: dexter <pmaier(a)sysmocom.de>
Hello Jenkins Builder,
I'd like you to reexamine a change. Please visit
https://gerrit.osmocom.org/c/pysim/+/40096?usp=email
to look at the new patch set (#2).
The following approvals got outdated and were removed:
Verified+1 by Jenkins Builder
Change subject: personalization: add param_source.py, implement batch personalization
......................................................................
personalization: add param_source.py, implement batch personalization
Implement pySim.esim.saip.personalization.BatchPersonalization,
generating N eSIM profiles from a preset configuration.
Batch parameters can be fed by a constant, incrementing, random or from
CSV rows: add pySim.esim.saip.param_source.* classes to feed such input
to each of the BatchPersonalization's ConfigurableParameter instances.
Related: SYS#6768
Change-Id: I497c60c101ea0eea980e8b1a4b1f36c0eda39002
---
A pySim/esim/saip/param_source.py
M pySim/esim/saip/personalization.py
2 files changed, 280 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/96/40096/2
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/40096?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I497c60c101ea0eea980e8b1a4b1f36c0eda39002
Gerrit-Change-Number: 40096
Gerrit-PatchSet: 2
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
neels has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/40093?usp=email )
Change subject: personalization: set some typical parameter names
......................................................................
personalization: set some typical parameter names
These names better match what humans expect to read, for example "PIN1"
instead of "Pin1".
(We still fall back to the __class__.__name__ if a subclass omits a
specific name, see the ConfigurableParameter init.)
Change-Id: I31f390d634e58c384589c50a33ca45d6f86d4e10
---
M pySim/esim/saip/personalization.py
1 file changed, 11 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/93/40093/1
diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py
index d09be46..a97706f 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -272,6 +272,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."""
+ name = 'ICCID'
min_len = 18
max_len = 20
@@ -290,6 +291,8 @@
class Imsi(DecimalParam):
"""Configurable IMSI. Expects value to be a string of digits. Automatically sets the ACC to
the last digit of the IMSI."""
+
+ name = 'IMSI'
min_len = 6
max_len = 15
@@ -473,9 +476,11 @@
f" cannot find pukCode with keyReference={cls.keyReference}")
class Puk1(Puk):
+ name = 'PUK1'
keyReference = 0x01
class Puk2(Puk):
+ name = 'PUK2'
keyReference = 0x81
class Pin(DecimalHexParam):
@@ -505,9 +510,11 @@
+ f' {cls.get_name()} cannot find pinCode with keyReference={cls.keyReference}')
class Pin1(Pin):
+ name = 'PIN1'
keyReference = 0x01
class Pin2(Pin):
+ name = 'PIN2'
keyReference = 0x81
@classmethod
@@ -523,9 +530,11 @@
+ f' {cls.get_name()} cannot find pinCode with keyReference={cls.keyReference} in {naa=}')
class Adm1(Pin):
+ name = 'ADM1'
keyReference = 0x0A
class Adm2(Pin):
+ name = 'ADM2'
keyReference = 0x0B
class AlgoConfig(ConfigurableParameter):
@@ -561,8 +570,10 @@
class K(BinaryParam, AlgoConfig):
"""use validate_val() from BinaryParam, and apply_val() from AlgoConfig"""
+ name = 'K'
algo_config_key = 'key'
allow_len = int(128/8) # length in bytes (from BinaryParam)
class Opc(K):
+ name = 'OPc'
algo_config_key = 'opc'
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/40093?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I31f390d634e58c384589c50a33ca45d6f86d4e10
Gerrit-Change-Number: 40093
Gerrit-PatchSet: 1
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
neels has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/40095?usp=email )
Change subject: personalization: discover all useful ConfigurableParameter subclasses
......................................................................
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 https://gerrit.osmocom.org/c/pysim/+/40095?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I4970657294130b6b65d50ff19ffbb9ebab3be609
Gerrit-Change-Number: 40095
Gerrit-PatchSet: 1
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>
neels has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/40096?usp=email )
Change subject: personalization: add param_source.py, implement batch personalization
......................................................................
personalization: add param_source.py, implement batch personalization
Implement pySim.esim.saip.personalization.BatchPersonalization,
generating N eSIM profiles from a preset configuration.
Batch parameters can be fed by a constant, incrementing, random or from
CSV rows: add pySim.esim.saip.param_source.* classes to feed such input
to each of the BatchPersonalization's ConfigurableParameter instances.
Related: SYS#6768
Change-Id: I497c60c101ea0eea980e8b1a4b1f36c0eda39002
---
A pySim/esim/saip/param_source.py
M pySim/esim/saip/personalization.py
2 files changed, 230 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/96/40096/1
diff --git a/pySim/esim/saip/param_source.py b/pySim/esim/saip/param_source.py
new file mode 100644
index 0000000..8fce881
--- /dev/null
+++ b/pySim/esim/saip/param_source.py
@@ -0,0 +1,167 @@
+# Implementation of SimAlliance/TCA Interoperable Profile handling: parameter sources for batch personalization.
+#
+# (C) 2025 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+#
+# Author: nhofmeyr(a)sysmocom.de
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import random
+from pySim.utils import all_subclasses_of
+
+class ParamSourceExn(Exception):
+ pass
+
+class ParamSourceExhaustedExn(ParamSourceExn):
+ pass
+
+class ParamSourceUndefinedExn(ParamSourceExn):
+ pass
+
+class ParamSource:
+ 'abstract parameter source'
+
+ # This name should be short but descriptive, useful for a user interface, like 'random decimal digits'.
+ name = 'none'
+
+ @classmethod
+ def get_all_implementations(cls, blacklist=None):
+ # return a set() so that multiple inheritance does not return dups
+ return set(c
+ for c in all_subclasses_of(cls)
+ if ((not blacklist) or (c not in blacklist))
+ )
+
+ @classmethod
+ def from_str(cls, s:str):
+ '''if a parameter source defines some string input magic, override this function.
+ For example, a RandomDigitSource derives the number of digits from the string length,
+ so the user can enter '0000' to get a four digit random number.'''
+ return cls(s)
+
+ def get_next(self, csv_row:dict=None):
+ '''return the next value from the parameter source.
+ When there are no more values from the source, raise a ParamSourceExhaustedExn.'''
+ raise ParamSourceExhaustedExn()
+
+
+class ConstantSource(ParamSource):
+ 'one value for all'
+ name = 'constant'
+
+ def __init__(self, val:str):
+ self.val = val
+
+ def get_next(self, csv_row:dict=None):
+ return self.val
+
+class RandomDigitSource(ParamSource):
+ 'return a different sequence of random decimal digits each'
+ name = 'random decimal digits'
+
+ def __init__(self, num_digits, first_value, last_value):
+ 'see from_str()'
+ num_digits = int(num_digits)
+ first_value = int(first_value)
+ last_value = int(last_value)
+ assert num_digits > 0
+ assert first_value <= last_value
+ self.num_digits = num_digits
+ self.val_first_last = (first_value, last_value)
+
+ def get_next(self, csv_row:dict=None):
+ val = random.randint(*self.val_first_last) # TODO secure random source?
+ return self.val_to_digit(val)
+
+ def val_to_digit(self, val:int):
+ return '%0*d' % (self.num_digits, val) # pylint: disable=consider-using-f-string
+
+ @classmethod
+ def from_str(cls, s:str):
+ if '..' in s:
+ first_str, last_str = s.split('..')
+ first_str = first_str.strip()
+ last_str = last_str.strip()
+ else:
+ first_str = s.strip()
+ last_str = None
+
+ first_value = int(first_str)
+ last_value = int(last_str) if last_str is not None else '9' * len(first_str)
+ return cls(num_digits=len(first_str), first_value=first_value, last_value=last_value)
+
+class RandomHexDigitSource(ParamSource):
+ 'return a different sequence of random hexadecimal digits each'
+ name = 'random hexadecimal digits'
+
+ def __init__(self, num_digits):
+ 'see from_str()'
+ num_digits = int(num_digits)
+ if num_digits < 1:
+ raise ValueError('zero number of digits')
+ # hex digits always come in two
+ if (num_digits & 1) != 0:
+ raise ValueError(f'hexadecimal value should have even number of digits, not {num_digits}')
+ self.num_digits = num_digits
+
+ def get_next(self, csv_row:dict=None):
+ val = random.randbytes(self.num_digits // 2) # TODO secure random source?
+ return val
+
+ @classmethod
+ def from_str(cls, s:str):
+ return cls(num_digits=len(s.strip()))
+
+class IncDigitSource(RandomDigitSource):
+ 'incrementing sequence of digits'
+ name = 'incrementing decimal digits'
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.next_val = None
+ self.reset()
+
+ def reset(self):
+ self.next_val = self.val_first_last[0]
+
+ def get_next(self, csv_row:dict=None):
+ val = self.next_val
+ if val is None:
+ raise ParamSourceExhaustedExn()
+
+ returnval = self.val_to_digit(val)
+
+ val += 1
+ if val > self.val_first_last[1]:
+ self.next_val = None
+ else:
+ self.next_val = val
+
+ return returnval
+
+class CsvSource(ParamSource):
+ 'apply a column from a CSV row, as passed in to ParamSource.get_next(csv_row)'
+ name = 'from CSV'
+
+ def __init__(self, csv_column, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.csv_column = csv_column
+
+ def get_next(self, csv_row:dict=None):
+ val = None
+ if csv_row:
+ val = csv_row.get(self.csv_column)
+ if not val:
+ raise ParamSourceUndefinedExn(f'no value for CSV column {self.csv_column!r}')
+ return val
diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py
index 26fcbf7..160962f 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -17,11 +17,13 @@
import abc
import io
-from typing import List, Tuple
+import copy
+from typing import List, Tuple, Generator
from osmocom.tlv import camel_to_snake
from pySim.utils import enc_iccid, enc_imsi, h2b, rpad, sanitize_iccid, all_subclasses_of
from pySim.esim.saip import ProfileElement, ProfileElementSequence
+from pySim.esim.saip import param_source
def remove_unwanted_tuples_from_list(l: List[Tuple], unwanted_keys: List[str]) -> List[Tuple]:
"""In a list of tuples, remove all tuples whose first part equals 'unwanted_key'."""
@@ -608,3 +610,63 @@
class Opc(K):
name = 'OPc'
algo_config_key = 'opc'
+
+
+class BatchPersonalization:
+
+ class ParamAndSrc:
+ 'tie a ConfigurableParameter to a source of actual values'
+ def __init__(self, param:ConfigurableParameter, src:param_source.ParamSource):
+ self.param = param
+ self.src = src
+
+ def __init__(self,
+ n:int,
+ src_pes:ProfileElementSequence,
+ params:list[ParamAndSrc]=None,
+ csv_rows:Generator=None,
+ ):
+ self.n = n
+ self.params = params or []
+ self.src_pes = src_pes
+ self.csv_rows = csv_rows
+
+ def add_param_and_src(self, param:ConfigurableParameter, src:param_source.ParamSource):
+ self.params.append(BatchPersonalization.ParamAndSrc(param=param, src=src))
+
+ def generate_profiles(self):
+ # get first row of CSV: column names
+ csv_columns = None
+ if self.csv_rows:
+ try:
+ csv_columns = next(self.csv_rows)
+ except StopIteration as e:
+ raise ValueError('the input CSV file appears to be empty') from e
+
+ for i in range(self.n):
+ csv_row = None
+ if self.csv_rows and csv_columns:
+ try:
+ csv_row_list = next(self.csv_rows)
+ except StopIteration as e:
+ raise ValueError(f'not enough rows in the input CSV for eSIM nr {i+1} of {self.n}') from e
+
+ csv_row = dict(zip(csv_columns, csv_row_list))
+
+ pes = copy.deepcopy(self.src_pes)
+
+ for p in self.params:
+ try:
+ input_value = p.src.get_next(csv_row=csv_row)
+ assert input_value is not None
+ value = p.param.__class__.validate_val(input_value)
+ p.param.__class__.apply_val(pes, value)
+ except (
+ TypeError,
+ ValueError,
+ KeyError,
+ ) as e:
+ raise ValueError(f'{p.param.name} fed by {p.src.name}: {e}'
+ f' (input_value={p.param.input_value!r} value={p.param.value!r})') from e
+
+ yield pes
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/40096?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I497c60c101ea0eea980e8b1a4b1f36c0eda39002
Gerrit-Change-Number: 40096
Gerrit-PatchSet: 1
Gerrit-Owner: neels <nhofmeyr(a)sysmocom.de>