mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-31 00:42:34 +03:00
Compare commits
43 Commits
neels/wip
...
neels/saip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d755fb3b8 | ||
|
|
d92c27c677 | ||
|
|
aec1a5513e | ||
|
|
982c1707fe | ||
|
|
6332bb4755 | ||
|
|
65d78c3d7d | ||
|
|
2700084057 | ||
|
|
e1f319ece4 | ||
|
|
ae50a130b0 | ||
|
|
1a22de2ba9 | ||
|
|
c259e8f78f | ||
|
|
80ddaa469c | ||
|
|
49996cc67c | ||
|
|
0729f5f787 | ||
|
|
0ba9ec8147 | ||
|
|
10e39aec25 | ||
|
|
c4150d1c38 | ||
|
|
f93e66a258 | ||
|
|
192b3678cb | ||
|
|
57671c976f | ||
|
|
704c30211a | ||
|
|
df5586edb6 | ||
|
|
d322a3bebc | ||
|
|
bde8c28a67 | ||
|
|
c14e9d6333 | ||
|
|
92aac2079f | ||
|
|
298ecaef12 | ||
|
|
48ef015490 | ||
|
|
23fef33e33 | ||
|
|
801050bb1f | ||
|
|
c4860fac04 | ||
|
|
44cc8e4f7a | ||
|
|
a8ae6edba3 | ||
|
|
27a972804a | ||
|
|
40aa99af62 | ||
|
|
8c8f7cd067 | ||
|
|
33ad83104a | ||
|
|
75cda31110 | ||
|
|
83411a547b | ||
|
|
6dd155edeb | ||
|
|
7cb681d0c4 | ||
|
|
3072ae58f0 | ||
|
|
91d10d6ba4 |
@@ -57,18 +57,15 @@ class BatchPersonalization:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class ParamAndSrc:
|
class ParamAndSrc:
|
||||||
"""tie a ConfigurableParameter to a source of actual values"""
|
'tie a ConfigurableParameter to a source of actual values'
|
||||||
def __init__(self, param: ConfigurableParameter, src: param_source.ParamSource):
|
def __init__(self, param: ConfigurableParameter, src: param_source.ParamSource):
|
||||||
if isinstance(param, type):
|
self.param = param
|
||||||
self.param_cls = param
|
|
||||||
else:
|
|
||||||
self.param_cls = param.__class__
|
|
||||||
self.src = src
|
self.src = src
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
n: int,
|
n: int,
|
||||||
src_pes: ProfileElementSequence,
|
src_pes: ProfileElementSequence,
|
||||||
params: list[ParamAndSrc]=[],
|
params: list[ParamAndSrc]=None,
|
||||||
csv_rows: Generator=None,
|
csv_rows: Generator=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -77,10 +74,10 @@ class BatchPersonalization:
|
|||||||
copied.
|
copied.
|
||||||
params: list of ParamAndSrc instances, defining a ConfigurableParameter and corresponding ParamSource to fill in
|
params: list of ParamAndSrc instances, defining a ConfigurableParameter and corresponding ParamSource to fill in
|
||||||
profile values.
|
profile values.
|
||||||
csv_rows: A generator (e.g. iter(list_of_rows)) producing all CSV rows one at a time, starting with a row
|
csv_rows: A list or generator producing all CSV rows one at a time, starting with a row containing the column
|
||||||
containing the column headers. This is compatible with the python csv.reader. Each row gets passed to
|
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
|
ParamSource.get_next(), such that ParamSource implementations can access the row items.
|
||||||
param_source.CsvSource.
|
See param_source.CsvSource.
|
||||||
"""
|
"""
|
||||||
self.n = n
|
self.n = n
|
||||||
self.params = params or []
|
self.params = params or []
|
||||||
@@ -88,7 +85,7 @@ class BatchPersonalization:
|
|||||||
self.csv_rows = csv_rows
|
self.csv_rows = csv_rows
|
||||||
|
|
||||||
def add_param_and_src(self, param:ConfigurableParameter, src:param_source.ParamSource):
|
def add_param_and_src(self, param:ConfigurableParameter, src:param_source.ParamSource):
|
||||||
self.params.append(BatchPersonalization.ParamAndSrc(param, src))
|
self.params.append(BatchPersonalization.ParamAndSrc(param=param, src=src))
|
||||||
|
|
||||||
def generate_profiles(self):
|
def generate_profiles(self):
|
||||||
# get first row of CSV: column names
|
# get first row of CSV: column names
|
||||||
@@ -115,10 +112,10 @@ class BatchPersonalization:
|
|||||||
try:
|
try:
|
||||||
input_value = p.src.get_next(csv_row=csv_row)
|
input_value = p.src.get_next(csv_row=csv_row)
|
||||||
assert input_value is not None
|
assert input_value is not None
|
||||||
value = p.param_cls.validate_val(input_value)
|
value = p.param.__class__.validate_val(input_value)
|
||||||
p.param_cls.apply_val(pes, value)
|
p.param.__class__.apply_val(pes, value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f'{p.param_cls.get_name()} fed by {p.src.name}: {e}') from e
|
raise ValueError(f'{p.param.name} fed by {p.src.name}: {e}') from e
|
||||||
|
|
||||||
yield pes
|
yield pes
|
||||||
|
|
||||||
@@ -132,7 +129,7 @@ class UppAudit(dict):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_der(cls, der: bytes, params: List, der_size=False, additional_sd_keys=False):
|
def from_der(cls, der: bytes, params: List, der_size=False, additional_sd_keys=False):
|
||||||
"""return a dict of parameter name and set of selected parameter values found in a DER encoded profile. Note:
|
'''return a dict of parameter name and set of selected parameter values found in a DER encoded profile. Note:
|
||||||
some ConfigurableParameter implementations return more than one key-value pair, for example, Imsi returns
|
some ConfigurableParameter implementations return more than one key-value pair, for example, Imsi returns
|
||||||
both 'IMSI' and 'IMSI-ACC' parameters.
|
both 'IMSI' and 'IMSI-ACC' parameters.
|
||||||
|
|
||||||
@@ -154,7 +151,7 @@ class UppAudit(dict):
|
|||||||
Scp80Kvn03. So we would not show kvn 0x04..0x0f in an audit. additional_sd_keys=True includes audits of all SD
|
Scp80Kvn03. So we would not show kvn 0x04..0x0f in an audit. additional_sd_keys=True includes audits of all SD
|
||||||
key KVN there may be in the UPP. This helps to spot SD keys that may already be present in a UPP template, with
|
key KVN there may be in the UPP. This helps to spot SD keys that may already be present in a UPP template, with
|
||||||
unexpected / unusual kvn.
|
unexpected / unusual kvn.
|
||||||
"""
|
'''
|
||||||
|
|
||||||
# make an instance of this class
|
# make an instance of this class
|
||||||
upp_audit = cls()
|
upp_audit = cls()
|
||||||
@@ -320,7 +317,7 @@ class BatchAudit(list):
|
|||||||
return batch_audit
|
return batch_audit
|
||||||
|
|
||||||
def to_csv_rows(self, headers=True, sort_key=None):
|
def to_csv_rows(self, headers=True, sort_key=None):
|
||||||
"""generator that yields all audits' values as rows, useful feed to a csv.writer."""
|
'''generator that yields all audits' values as rows, useful feed to a csv.writer.'''
|
||||||
columns = set()
|
columns = set()
|
||||||
for audit in self:
|
for audit in self:
|
||||||
columns.update(audit.keys())
|
columns.update(audit.keys())
|
||||||
|
|||||||
@@ -37,10 +37,13 @@ class ParamSource:
|
|||||||
name = "none"
|
name = "none"
|
||||||
numeric_base = None # or 10 or 16
|
numeric_base = None # or 10 or 16
|
||||||
|
|
||||||
def __init__(self, input_str:str):
|
@classmethod
|
||||||
"""Subclasses should call super().__init__(input_str) before evaluating self.input_str. Each subclass __init__()
|
def from_str(cls, s:str):
|
||||||
may in turn manipulate self.input_str to apply expansions or decodings."""
|
"""Subclasses implement this:
|
||||||
self.input_str = input_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):
|
def get_next(self, csv_row:dict=None):
|
||||||
"""Subclasses implement this: return the next value from the parameter source.
|
"""Subclasses implement this: return the next value from the parameter source.
|
||||||
@@ -48,81 +51,78 @@ class ParamSource:
|
|||||||
This default implementation is an empty source."""
|
This default implementation is an empty source."""
|
||||||
raise ParamSourceExhaustedExn()
|
raise ParamSourceExhaustedExn()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_str(cls, input_str:str):
|
|
||||||
"""compatibility with earlier version of ParamSource. Just use the constructor."""
|
|
||||||
return cls(input_str)
|
|
||||||
|
|
||||||
class ConstantSource(ParamSource):
|
class ConstantSource(ParamSource):
|
||||||
"""one value for all"""
|
"""one value for all"""
|
||||||
name = "constant"
|
name = "constant"
|
||||||
|
|
||||||
|
def __init__(self, val:str):
|
||||||
|
self.val = val
|
||||||
|
|
||||||
def get_next(self, csv_row:dict=None):
|
def get_next(self, csv_row:dict=None):
|
||||||
return self.input_str
|
return self.val
|
||||||
|
|
||||||
class InputExpandingParamSource(ParamSource):
|
class InputExpandingParamSource(ParamSource):
|
||||||
|
|
||||||
def __init__(self, input_str:str):
|
|
||||||
super().__init__(input_str)
|
|
||||||
self.input_str = self.expand_input_str(self.input_str)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def expand_input_str(cls, input_str:str):
|
def expand_str(cls, s:str):
|
||||||
# user convenience syntax '0*32' becomes '00000000000000000000000000000000'
|
# user convenience syntax '0*32' becomes '00000000000000000000000000000000'
|
||||||
if "*" not in input_str:
|
if "*" not in s:
|
||||||
return input_str
|
return s
|
||||||
# re: "XX * 123" with optional spaces
|
tokens = re.split(r"([^ \t]+)[ \t]*\*[ \t]*([0-9]+)", s)
|
||||||
tokens = re.split(r"([^ \t]+)[ \t]*\*[ \t]*([0-9]+)", input_str)
|
|
||||||
if len(tokens) < 3:
|
if len(tokens) < 3:
|
||||||
return input_str
|
return s
|
||||||
parts = []
|
parts = []
|
||||||
for unchanged, snippet, repeat_str in zip(tokens[0::3], tokens[1::3], tokens[2::3]):
|
for unchanged, snippet, repeat_str in zip(tokens[0::3], tokens[1::3], tokens[2::3]):
|
||||||
parts.append(unchanged)
|
parts.append(unchanged)
|
||||||
repeat = int(repeat_str)
|
repeat = int(repeat_str)
|
||||||
parts.append(snippet * repeat)
|
parts.append(snippet * repeat)
|
||||||
|
|
||||||
return "".join(parts)
|
return "".join(parts)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, s:str):
|
||||||
|
return cls(cls.expand_str(s))
|
||||||
|
|
||||||
class DecimalRangeSource(InputExpandingParamSource):
|
class DecimalRangeSource(InputExpandingParamSource):
|
||||||
"""abstract: decimal numbers with a value range"""
|
"""abstract: decimal numbers with a value range"""
|
||||||
|
|
||||||
numeric_base = 10
|
numeric_base = 10
|
||||||
|
|
||||||
def __init__(self, input_str:str=None, num_digits:int=None, first_value:int=None, last_value:int=None):
|
def __init__(self, num_digits, first_value, last_value):
|
||||||
"""Constructor to set up values from a (user entered) string: DecimalRangeSource(input_str).
|
|
||||||
Constructor to set up values directly: DecimalRangeSource(num_digits=3, first_value=123, last_value=456)
|
|
||||||
|
|
||||||
num_digits produces leading zeros when first_value..last_value are shorter.
|
|
||||||
"""
|
"""
|
||||||
assert ((input_str is not None and (num_digits, first_value, last_value) == (None, None, None))
|
See also from_str().
|
||||||
or (input_str is None and None not in (num_digits, first_value, last_value)))
|
|
||||||
|
|
||||||
if input_str is not None:
|
|
||||||
super().__init__(input_str)
|
|
||||||
|
|
||||||
input_str = self.input_str
|
|
||||||
|
|
||||||
if ".." in input_str:
|
|
||||||
first_str, last_str = input_str.split('..')
|
|
||||||
first_str = first_str.strip()
|
|
||||||
last_str = last_str.strip()
|
|
||||||
else:
|
|
||||||
first_str = input_str.strip()
|
|
||||||
last_str = None
|
|
||||||
|
|
||||||
num_digits = len(first_str)
|
|
||||||
first_value = int(first_str)
|
|
||||||
last_value = int(last_str if last_str is not None else "9" * num_digits)
|
|
||||||
|
|
||||||
|
All arguments are integer values, and are converted to int if necessary, so a string of an integer is fine.
|
||||||
|
num_digits: fixed number of digits (possibly with leading zeros) to generate.
|
||||||
|
first_value, last_value: the decimal range in which to provide digits.
|
||||||
|
"""
|
||||||
|
num_digits = int(num_digits)
|
||||||
|
first_value = int(first_value)
|
||||||
|
last_value = int(last_value)
|
||||||
assert num_digits > 0
|
assert num_digits > 0
|
||||||
assert first_value <= last_value
|
assert first_value <= last_value
|
||||||
self.num_digits = num_digits
|
self.num_digits = num_digits
|
||||||
self.first_value = first_value
|
self.val_first_last = (first_value, last_value)
|
||||||
self.last_value = last_value
|
|
||||||
|
|
||||||
def val_to_digit(self, val:int):
|
def val_to_digit(self, val:int):
|
||||||
return "%0*d" % (self.num_digits, val) # pylint: disable=consider-using-f-string
|
return "%0*d" % (self.num_digits, val) # pylint: disable=consider-using-f-string
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, s:str):
|
||||||
|
s = cls.expand_str(s)
|
||||||
|
|
||||||
|
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 RandomSourceMixin:
|
class RandomSourceMixin:
|
||||||
random_impl = secrets.SystemRandom()
|
random_impl = secrets.SystemRandom()
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ class RandomDigitSource(DecimalRangeSource, RandomSourceMixin):
|
|||||||
# try to generate random digits that are always different from previously produced random bytes
|
# try to generate random digits that are always different from previously produced random bytes
|
||||||
attempts = 10
|
attempts = 10
|
||||||
while True:
|
while True:
|
||||||
val = self.random_impl.randint(self.first_value, self.last_value)
|
val = self.random_impl.randint(*self.val_first_last)
|
||||||
if val in RandomDigitSource.used_keys:
|
if val in RandomDigitSource.used_keys:
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
if attempts:
|
if attempts:
|
||||||
@@ -150,11 +150,9 @@ class RandomHexDigitSource(InputExpandingParamSource, RandomSourceMixin):
|
|||||||
numeric_base = 16
|
numeric_base = 16
|
||||||
used_keys = set()
|
used_keys = set()
|
||||||
|
|
||||||
def __init__(self, input_str:str):
|
def __init__(self, num_digits):
|
||||||
super().__init__(input_str)
|
"""see from_str()"""
|
||||||
input_str = self.input_str
|
num_digits = int(num_digits)
|
||||||
|
|
||||||
num_digits = len(input_str.strip())
|
|
||||||
if num_digits < 1:
|
if num_digits < 1:
|
||||||
raise ValueError("zero number of digits")
|
raise ValueError("zero number of digits")
|
||||||
# hex digits always come in two
|
# hex digits always come in two
|
||||||
@@ -176,20 +174,23 @@ class RandomHexDigitSource(InputExpandingParamSource, RandomSourceMixin):
|
|||||||
|
|
||||||
return b2h(val)
|
return b2h(val)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, s:str):
|
||||||
|
s = cls.expand_str(s)
|
||||||
|
return cls(num_digits=len(s.strip()))
|
||||||
|
|
||||||
class IncDigitSource(DecimalRangeSource):
|
class IncDigitSource(DecimalRangeSource):
|
||||||
"""incrementing sequence of digits"""
|
"""incrementing sequence of digits"""
|
||||||
name = "incrementing decimal digits"
|
name = "incrementing decimal digits"
|
||||||
|
|
||||||
def __init__(self, input_str:str=None, num_digits:int=None, first_value:int=None, last_value:int=None):
|
def __init__(self, num_digits, first_value, last_value):
|
||||||
"""input_str: the first value to return, a string of an integer number with optional leading zero digits. The
|
super().__init__(num_digits, first_value, last_value)
|
||||||
leading zero digits are preserved."""
|
|
||||||
super().__init__(input_str, num_digits, first_value, last_value)
|
|
||||||
self.next_val = None
|
self.next_val = None
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Restart from the first value of the defined range passed to __init__()."""
|
"""Restart from the first value of the defined range passed to __init__()."""
|
||||||
self.next_val = self.first_value
|
self.next_val = self.val_first_last[0]
|
||||||
|
|
||||||
def get_next(self, csv_row:dict=None):
|
def get_next(self, csv_row:dict=None):
|
||||||
val = self.next_val
|
val = self.next_val
|
||||||
@@ -199,7 +200,7 @@ class IncDigitSource(DecimalRangeSource):
|
|||||||
returnval = self.val_to_digit(val)
|
returnval = self.val_to_digit(val)
|
||||||
|
|
||||||
val += 1
|
val += 1
|
||||||
if val > self.last_value:
|
if val > self.val_first_last[1]:
|
||||||
self.next_val = None
|
self.next_val = None
|
||||||
else:
|
else:
|
||||||
self.next_val = val
|
self.next_val = val
|
||||||
@@ -210,15 +211,13 @@ class CsvSource(ParamSource):
|
|||||||
"""apply a column from a CSV row, as passed in to ParamSource.get_next(csv_row)"""
|
"""apply a column from a CSV row, as passed in to ParamSource.get_next(csv_row)"""
|
||||||
name = "from CSV"
|
name = "from CSV"
|
||||||
|
|
||||||
def __init__(self, input_str:str):
|
def __init__(self, csv_column):
|
||||||
"""self.csv_column = input_str:
|
|
||||||
column name indicating the column to use for this parameter.
|
|
||||||
This name is used in get_next(): the caller passes the current CSV row to get_next(), from which
|
|
||||||
CsvSource picks the column with the name matching csv_column.
|
|
||||||
"""
|
"""
|
||||||
"""Parse input_str into self.num_digits, self.first_value, self.last_value."""
|
csv_column: column name indicating the column to use for this parameter.
|
||||||
super().__init__(input_str)
|
This name is used in get_next(): the caller passes the current CSV row to get_next(), from which
|
||||||
self.csv_column = self.input_str
|
CsvSource picks the column with the name matching csv_column.
|
||||||
|
"""
|
||||||
|
self.csv_column = csv_column
|
||||||
|
|
||||||
def get_next(self, csv_row:dict=None):
|
def get_next(self, csv_row:dict=None):
|
||||||
val = None
|
val = None
|
||||||
|
|||||||
@@ -330,7 +330,6 @@ class DecimalHexParam(DecimalParam):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def validate_val(cls, val):
|
def validate_val(cls, val):
|
||||||
val = super().validate_val(val)
|
val = super().validate_val(val)
|
||||||
assert isinstance(val, str)
|
|
||||||
val = ''.join('%02x' % ord(x) for x in val)
|
val = ''.join('%02x' % ord(x) for x in val)
|
||||||
if cls.rpad is not None:
|
if cls.rpad is not None:
|
||||||
c = cls.rpad_char
|
c = cls.rpad_char
|
||||||
@@ -340,7 +339,7 @@ class DecimalHexParam(DecimalParam):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def decimal_hex_to_str(cls, val):
|
def decimal_hex_to_str(cls, val):
|
||||||
"""useful for get_values_from_pes() implementations of subclasses"""
|
'useful for get_values_from_pes() implementations of subclasses'
|
||||||
if isinstance(val, bytes):
|
if isinstance(val, bytes):
|
||||||
val = b2h(val)
|
val = b2h(val)
|
||||||
assert isinstance(val, hexstr)
|
assert isinstance(val, hexstr)
|
||||||
@@ -620,7 +619,7 @@ class SmspTpScAddr(ConfigurableParameter):
|
|||||||
# ensure the parameter_indicators.tp_sc_addr is True
|
# ensure the parameter_indicators.tp_sc_addr is True
|
||||||
ef_smsp_dec['parameter_indicators']['tp_sc_addr'] = True
|
ef_smsp_dec['parameter_indicators']['tp_sc_addr'] = True
|
||||||
# re-encode into the File body
|
# re-encode into the File body
|
||||||
f_smsp.body = ef_smsp.encode_record_bin(ef_smsp_dec, 1, 52)
|
f_smsp.body = ef_smsp.encode_record_bin(ef_smsp_dec, 1)
|
||||||
#print("SMSP (new): %s" % f_smsp.body)
|
#print("SMSP (new): %s" % f_smsp.body)
|
||||||
# re-generate the pe.decoded member from the File instance
|
# re-generate the pe.decoded member from the File instance
|
||||||
pe.file2pe(f_smsp)
|
pe.file2pe(f_smsp)
|
||||||
@@ -693,25 +692,24 @@ class MncLen(ConfigurableParameter):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_values_from_pes(cls, pes: ProfileElementSequence):
|
def get_values_from_pes(cls, pes: ProfileElementSequence):
|
||||||
for naa in ('isim',):# 'isim', 'csim'):
|
for pe in pes.get_pes_for_type('usim'):
|
||||||
for pe in pes.get_pes_for_type(naa):
|
if not hasattr(pe, 'files'):
|
||||||
if not hasattr(pe, 'files'):
|
continue
|
||||||
continue
|
f_ad = pe.files.get('ef-ad', None)
|
||||||
f_ad = pe.files.get('ef-ad', None)
|
if f_ad is None:
|
||||||
if f_ad is None:
|
continue
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ef_ad = EF_AD()
|
ef_ad = EF_AD()
|
||||||
ef_ad_dec = ef_ad.decode_bin(f_ad.body)
|
ef_ad_dec = ef_ad.decode_bin(f_ad.body)
|
||||||
except StreamError:
|
except StreamError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
mnc_len = ef_ad_dec.get('mnc_len', None)
|
mnc_len = ef_ad_dec.get('mnc_len', None)
|
||||||
if mnc_len is None:
|
if mnc_len is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
yield { cls.name: str(mnc_len) }
|
yield { cls.name: str(mnc_len) }
|
||||||
|
|
||||||
|
|
||||||
class SdKey(BinaryParam):
|
class SdKey(BinaryParam):
|
||||||
|
|||||||
@@ -267,6 +267,16 @@ class ConfigurableParameterTest(unittest.TestCase):
|
|||||||
'11111111111111111111111111111111'
|
'11111111111111111111111111111111'
|
||||||
'22222222222222222222222222222222'),
|
'22222222222222222222222222222222'),
|
||||||
|
|
||||||
|
|
||||||
|
Paramtest(param_cls=p13n.MncLen,
|
||||||
|
val='2',
|
||||||
|
expect_clean_val=2,
|
||||||
|
expect_val='2'),
|
||||||
|
Paramtest(param_cls=p13n.MncLen,
|
||||||
|
val=3,
|
||||||
|
expect_clean_val=3,
|
||||||
|
expect_val='3'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
for sdkey_cls in (
|
for sdkey_cls in (
|
||||||
|
|||||||
Reference in New Issue
Block a user