neels has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/41845?usp=email )
Change subject: rather move BatchPersonalization to separate module ...................................................................... rather move BatchPersonalization to separate module Change-Id: I01ae40a06605eb205bfb409189fcd2b3a128855a --- A pySim/esim/saip/batch.py M pySim/esim/saip/personalization.py 2 files changed, 117 insertions(+), 95 deletions(-) git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/45/41845/1 diff --git a/pySim/esim/saip/batch.py b/pySim/esim/saip/batch.py new file mode 100644 index 0000000..0355ad2 --- /dev/null +++ b/pySim/esim/saip/batch.py @@ -0,0 +1,117 @@ +"""Implementation of Personalization of eSIM profiles in SimAlliance/TCA Interoperable Profile: + Run a batch of N personalizations""" + +# (C) 2025-2026 by sysmocom - s.f.m.c. GmbH <[email protected]> +# +# Author: [email protected] +# +# 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 copy +from typing import Generator +from pysim.esim.saip.personalization import ConfigurableParameter +from pySim.esim.saip import param_source +from pySim.esim.saip import ProfileElementSequence + +class BatchPersonalization: + """Produce a series of eSIM profiles from predefined parameters. + Personalization parameters are derived from pysim.esim.saip.param_source.ParamSource. + + Usage example: + + der_input = some_file.open('rb').read() + pes = ProfileElementSequence.from_der(der_input) + p = pers.BatchPersonalization( + n=10, + src_pes=pes, + csv_rows=get_csv_reader()) + + p.add_param_and_src( + personalization.Iccid(), + param_source.IncDigitSource( + num_digits=18, + first_value=123456789012340001, + last_value=123456789012340010)) + + # add more parameters here, using ConfigurableParameter and ParamSource subclass instances to define the profile + # ... + + # generate all 10 profiles (from n=10 above) + for result_pes in p.generate_profiles(): + upp = result_pes.to_der() + store_upp(upp) + """ + + 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, + ): + """ + n: number of eSIM profiles to generate. + src_pes: a decoded eSIM profile as ProfileElementSequence, to serve as template. This is not modified, only + copied. + params: list of ParamAndSrc instances, defining a ConfigurableParameter and corresponding ParamSource to fill in + profile values. + csv_rows: A list or generator producing all CSV rows one at a time, starting with a row containing the column + headers. This is compatible with the python csv.reader. Each row gets passed to + ParamSource.get_next(), such that ParamSource implementations can access the row items. + See param_source.CsvSource. + """ + 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 Exception as e: + raise ValueError(f'{p.param.name} fed by {p.src.name}: {e}') from e + + yield pes diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py index 550cb6c..e024163 100644 --- a/pySim/esim/saip/personalization.py +++ b/pySim/esim/saip/personalization.py @@ -17,14 +17,12 @@ import abc import io -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 from pySim.esim.saip import ProfileElement, ProfileElementSequence from pySim.ts_51_011 import EF_SMSP -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'.""" @@ -666,96 +664,3 @@ min_val = 1 max_val = 255 example_input = '1' - - -class BatchPersonalization: - """Produce a series of eSIM profiles from predefined parameters. - Personalization parameters are derived from pysim.esim.saip.param_source.ParamSource. - - Usage example: - - der_input = some_file.open('rb').read() - pes = ProfileElementSequence.from_der(der_input) - p = pers.BatchPersonalization( - n=10, - src_pes=pes, - csv_rows=get_csv_reader()) - - p.add_param_and_src( - personalization.Iccid(), - param_source.IncDigitSource( - num_digits=18, - first_value=123456789012340001, - last_value=123456789012340010)) - - # add more parameters here, using ConfigurableParameter and ParamSource subclass instances to define the profile - # ... - - # generate all 10 profiles (from n=10 above) - for result_pes in p.generate_profiles(): - upp = result_pes.to_der() - store_upp(upp) - """ - - 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, - ): - """ - n: number of eSIM profiles to generate. - src_pes: a decoded eSIM profile as ProfileElementSequence, to serve as template. This is not modified, only - copied. - params: list of ParamAndSrc instances, defining a ConfigurableParameter and corresponding ParamSource to fill in - profile values. - csv_rows: A list or generator producing all CSV rows one at a time, starting with a row containing the column - headers. This is compatible with the python csv.reader. Each row gets passed to - ParamSource.get_next(), such that ParamSource implementations can access the row items. - See param_source.CsvSource. - """ - 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 Exception as e: - raise ValueError(f'{p.param.name} fed by {p.src.name}: {e}') from e - - yield pes -- To view, visit https://gerrit.osmocom.org/c/pysim/+/41845?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: I01ae40a06605eb205bfb409189fcd2b3a128855a Gerrit-Change-Number: 41845 Gerrit-PatchSet: 1 Gerrit-Owner: neels <[email protected]>
