Compare commits

..

70 Commits

Author SHA1 Message Date
Neels Hofmeyr 231358bcd3 personalization: add GfmSuciRi, GfmSuciCalcInfo, and test
Change-Id: I1b69debf5992aa715171b43b30864dc152dc556f
2026-06-22 18:52:13 +02:00
Neels Hofmeyr 80d3364706 SAIP2.1_gfmsuci.der
Change-Id: I69354754013e4a98d86a9a8abdd7a6a36eb95177
2026-06-19 05:29:59 +02:00
Neels Hofmeyr 15208ba345 test_configurable_parameters: test less templates
Change-Id: Ib75b6919a3acfddd99bf9baa9b6847ef731b9e67
2026-06-19 05:26:05 +02:00
Neels Hofmeyr 091615f516 debug log for rebuild_mandatory_services()
Change-Id: Ie53c84247a9524a81b475b3b02b5330207c15601
2026-06-13 01:43:00 +02:00
Neels Hofmeyr eafedb21a4 saip BatchPersonalization: call rebuild_mandatory_services()
Particular reason: when manipulating the 5G SUCI parameters, the
mandatory services get-identity, profile-a-x25519 and profile-b-p256 may
need to be reconfigured.

In general, it is a good idea to run these checks anyway.

Change-Id: I5e6eef0f1845a25cddb03af8d16c40e305bcdc1f
2026-06-13 01:43:00 +02:00
Neels Hofmeyr 60239571d6 saip.PES.rebuild_mandatory_services(): set 5G get-identity, profile-a-x25519, profile-b-p256
Related: SYS#8096 SYS#8037
Change-Id: Ibc29c6437c5c92e2b14938b733156536863465c1
2026-06-13 01:43:00 +02:00
Neels Hofmeyr ccc9b5a7b2 saip/personalization: add EuiccMandatoryServiceParam for 3 services
Change-Id: Icddeb2488c4a024c6ee5afcc1b6c8cc0e436c43c
2026-06-12 03:29:45 +02:00
Neels Hofmeyr d58a70865c saip: add ProfileElementHeader.mandatory_service_present() probe
Change-Id: I5a28a50c767bd2dd0e5a7954e3633743a0155856
2026-06-12 03:29:45 +02:00
Neels Hofmeyr 7f3b6a9cb1 typo 'concetenation' in personalization.py
Change-Id: I51345db014335e8a70a7437a9cad5a3e47570a95
2026-06-12 03:29:45 +02:00
Neels Hofmeyr ab57dd8cb8 personalization: SUCI: ff-pad remaining file size
Change-Id: I9465eac9269e3f76dddd467109f547489a0feb93
2026-06-12 03:29:45 +02:00
Neels Hofmeyr 3fd0c96081 add SUCI parameters
Change-Id: I3c0793b8a67bbd0c8247784bd3b5cbd265f94ec2
2026-06-12 03:29:45 +02:00
Neels Hofmeyr 13530c6fe9 tweak test_configurable_parameters.py: add iff_present flag
apply a parameter only when it exists in the template, will be useful
for suci

Change-Id: I5811ecde4c4e880bb8dbd22fffe23faafcfe36ad
2026-05-21 13:27:42 +02:00
Neels Hofmeyr 27def57be2 tweak test_configurable_parameters.py: show value found in template
Change-Id: If9039bbb8547ee24ae784a932f60cd5de6c9247b
2026-05-21 13:27:42 +02:00
Neels Hofmeyr 2c3820c39f tweak test_configurable_parameters.py: saner output composition.
Change-Id: Id3e3b46b2b3d7919a75c620803ce28d2a715008b
2026-05-21 13:27:42 +02:00
Neels Hofmeyr b8871ed04e TuakNumberOfKeccak: remove numeric_base to hide random number sources
Change-Id: I913878e3f05ad1e39ff45da75c67582a6a1f930f
2026-05-21 13:27:42 +02:00
Neels Hofmeyr d9e0adbe1a saip/batch: add blacklist to to_csv_rows()
Change-Id: I5e567e59a007cf4b5d75a4dcea5371ff4404bf24
2026-05-21 13:27:42 +02:00
Neels Hofmeyr e04db4b7fb ConfigurableParameter.get_typical_input_len: limit to 10 lines
Change-Id: Ia3d79e786f397a02bf2a8fafac5030d1198d9f76
2026-05-21 13:27:42 +02:00
Neels Hofmeyr ac2fe707e4 xo/test_configurable_parameters
Change-Id: I15573e801a62f94f0701637562e2d64a212041ca
2026-05-21 13:27:42 +02:00
Neels Hofmeyr ec6f079a94 personalization: EF_SMSP: keep same length as found in template
Change-Id: Id24752101ae82c4986209f4103cc9cbdcce8ce1d
2026-05-21 13:27:42 +02:00
Neels Hofmeyr a9fe3e2fde personalization: fix EF_SMSP length, alpha_id padding
The efFileSize needs to be updated and the alpha_id needs to be != None.

Change-Id: Ief6e02517f3e96158a2509d763b88aec4bd5a296
2026-05-21 13:27:42 +02:00
Neels Hofmeyr 68dd9e6140 saip.batch: log parameter errors
Change-Id: I6a46b2dc9018078ab8361226d1e6b50d3b4e1aaa
2026-05-21 13:27:42 +02:00
Neels Hofmeyr 947a0ab723 personalization: add MncLen ConfigurableParameter
Implement as EnumParam because it only has two possible values

Change-Id: I6c600faeab00ffb072acbe94c9a8b2d1397c07d3
2026-05-21 13:27:24 +02:00
Neels Hofmeyr e9c68df3c6 sdkeys kv40 aes
Change-Id: If5b53c840ebd1f224f9bb4706a602b415194f47b
2026-05-12 15:59:40 +02:00
Neels Hofmeyr a2ffe30542 esim/http_json_api.py: support text/plain response Content-Type
Allow returning text/plain Content-Types as 'data' output argument.

So far, all the esim/http_json_api functions require a JSON response.
However, a specific vendor has a list function where the request is JSON
but the response is text/plain CSV data. Allow and return in a dict.

Change-Id: Iba6e4cef1048b376050a435a900c0f395655a790
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 050b08157d Revert "esim/http_json_api: extend JSON API with server functionality"
This reverts commit e00c0becca.
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 035c49100b Revert "esim/http_json_api: add missing apidoc"
This reverts commit 0a1c5a27d7.
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 2e310ee825 Revert "http_json_api: Only require Content-Type if response body is non-empty"
This reverts commit e0a9e73267.
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 41ae4db9bb Revert "esim/http_json_api: add alternative API interface"
This reverts commit f9d7c82b4d.
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 6a6dacef51 Revert "esim/http_json_api: add alternative API interface (follow up)"
This reverts commit 8b2a49aa8e.
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 270c148343 Revert "esim/http_json_api: allow URL rewriting"
This reverts commit 0634f77308.
2026-05-12 15:59:40 +02:00
Neels Hofmeyr a07e4de853 saip: add numeric_base indicator to ConfigurableParameter and ParamSource
By default, numeric_base = None, to indicate that there are no explicit
limitations on the number space.

For parameters that are definitely decimal, set numeric_base = 10.
For definitely hexadecimal, set numeric_base = 16.

Do the same for ConfigurableParameter as well as ParamSource, so callers
can match them up: if a parameter is numeric_base = 10, then omit
sources that are numeric_base = 16, and vice versa.

Change-Id: Ib0977bbdd9a85167be7eb46dd331fedd529dae01
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 6e2498eac0 saip SmspTpScAddr.get_values_from_pes: allow empty values
Change-Id: Ibbdd08f96160579238b50699091826883f2e9f5a
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 1173b11ca6 SdKey KVN4X ID02: set key_usage_qual=0x48
Related: SYS#7865
Change-Id: Idc5d33a4a003801f60c95fff6931706a9aeb6692
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 1d79baca35 saip: SdKey.__doc__: update SdKey listing
Change-Id: Ib5011b0c7d76b082231744cf09077628dc4e69b7
2026-05-12 15:59:40 +02:00
Neels Hofmeyr abe4a8f9b1 esim.saip.personalization: fix TLSPSK keys
Add AES variant of TLSPSK DEK (SCP81 KVN40 key_id=0x02).

Change-Id: I713a008fd26bbfcf437e0f29717b753f058ce76a
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 429656dcc6 add comment about not updating existing key_usage_qualifier
Change-Id: Ie23ae5fde17be6b37746784bf1601b4d0874397a
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 74f66fecdb test_configurable_parameters.py: add tests for new parameters
For:
SmspTpScAddr
MilenageRotation
MilenageXoringConstants
TuakNrOfKeccak

Change-Id: Iecbea14fe31a9ee08d871dcde7f295d26d7bd001
2026-05-12 15:59:40 +02:00
Neels Hofmeyr bfdcdbfc23 saip: SmspTpScAddr: fix get_values_from_pes
Change-Id: I2010305340499c907bb7618c04c61e194db34814
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 87beb049a4 ConfigurableParameter: safer val length check
Change-Id: Ibe91722ed1477b00d20ef5e4e7abd9068ff2f3e4
2026-05-12 15:59:40 +02:00
Neels Hofmeyr cf6c47dba2 UppAudit: better indicate exception cause
Change-Id: I4d986b89a473a5b12ed56b4710263b034876a33e
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 10d42458a4 remove transitional name mapping
This reverts commit I974cb6c393a2ed2248a6240c2722d157e9235c33

Now, finally, all SdKey classes have a unified logical naming scheme.

Change-Id: Ic185af4a903c2211a5361d023af9e7c6fc57ae78
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 0cab988f47 transitional name mapping
To help existing applications transition to a common naming scheme for
the SdKey classes, offer this intermediate result, where the SdKey
classes' .name are still unchanged as before generating them.

Change-Id: I974cb6c393a2ed2248a6240c2722d157e9235c33
2026-05-12 15:59:40 +02:00
Neels Hofmeyr a259c45e7a generate sdkey classes from a list
Change-Id: Ic92ddea6e1fad8167ea75baf78ffc3eb419838c4
2026-05-12 15:59:40 +02:00
Neels Hofmeyr ff02cd8c78 saip SmspTpScAddr: safeguard against decoding error
Reading the TS48 V6.0 eSIM_GTP_SAIP2.1A_NoBERTLV profile results in an
exception [1] in SmspTpScAddr. I have a caller that needs to skip
erratic values instead of raising.

The underlying issue, I presume, is that either the data needs
validation before decode_record_bin(), or decode_record_bin() needs
well-defined error handling.

So far I know only of this IndexError, so, as a workaround, catch that.

[1]
  File "/pysim/pySim/esim/saip/personalization.py", line 617, in get_values_from_pes
    ef_smsp_dec = ef_smsp.decode_record_bin(f_smsp.body, 1)
  File "/pysim/pySim/filesystem.py", line 1047, in decode_record_bin
    return parse_construct(self._construct, raw_bin_data)
  File "/application/venv/lib/python3.13/site-packages/osmocom/construct.py", line 550, in parse_construct
    parsed = c.parse(raw_bin_data, total_len=length, **context)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 404, in parse
    return self.parse_stream(io.BytesIO(data), **contextkw)
           ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 416, in parse_stream
    return self._parsereport(stream, context, "(parsing)")
           ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
  File "/application/venv/lib/python3.13/site-packages/construct/core.py", line 820, in _parse
    return self._decode(obj, context, path)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/application/venv/lib/python3.13/site-packages/osmocom/construct.py", line 268, in _decode
    if r[-1] == 'f':
       ~^^^^
  File "/application/venv/lib/python3.13/site-packages/osmocom/utils.py", line 50, in __getitem__
    return hexstr(super().__getitem__(val))
                  ~~~~~~~~~~~~~~~~~~~^^^^^
IndexError: string index out of range

Change-Id: Ic436e206776b81f24de126e8ee0ae8bf5f3e8d7a
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 54c202a7ca saip/param_source: try to not repeat random values
Change-Id: I4fa743ef5677580f94b9df16a5051d1d178edeb0
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 257f8c2598 use secrets.SystemRandom as secure random nr source
secrets.SystemRandom is defined as the most secure random source
available on the given operating system.

Change-Id: I8049cd1292674b3ced82b0926569128535af6efe
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 45c88d52b3 use random.SystemRandom as random nr source (/dev/urandom)
/dev/urandom is somewhat better than python's PRNG

Change-Id: I6de38c14ac6dd55bc84d53974192509c18d02bfa
2026-05-12 15:59:40 +02:00
Neels Hofmeyr ee09e15e5d add test_param_src.py
Change-Id: I03087b84030fddae98b965e0075d44e04ec6ba5c
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 4e12d17238 param_source: allow plugging a random implementation (for testing)
Change-Id: Idce2b18af70c17844d6f09f7704efc869456ac39
2026-05-12 15:59:40 +02:00
Neels Hofmeyr f18cf6d2c7 RandomHexDigitSource: rather return in string format, not bytes
Change-Id: I4e86289f6fb72cbd4cf0c90b8b49538cfab69a7f
2026-05-12 15:59:40 +02:00
Neels Hofmeyr d8768aa7bf personalization: add int as input type for BinaryParameter
Change-Id: I31d8142cb0847a8b291f8dc614d57cb4734f0190
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 72556426f8 personalization.ConfigurableParameter: fix BytesIO() input
Change-Id: I0ad160eef9015e76eef10baee7c6b606fe249123
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 8b07326265 add test_configurable_parameters.py
Change-Id: Ia55f0d11f8197ca15a948a83a34b3488acf1a0b4
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 4d23824a81 ConfigurableParameter: do not magically overwrite the 'name' attribute
Change-Id: I6f631444c6addeb7ccc5f6c55b9be3dc83409169
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 0a559d2c6e personalization audit: optionally audit all (unknown) SD keys
By a flag, allow to audit also all Security Domain KVN that we have
*not* created ConfigurableParameter subclasses for.

For example, SCP80 has reserved kvn 0x01..0x0f, but we offer only
Scp80Kvn01, Scp80Kvn02, Scp80Kvn03. So we would not show kvn
0x03..0x0f in an audit.

This patch includes audits of all SD key kvn there may be in the UPP.
This will help to spot SD keys that may already be present in a UPP
template, with unexpected / unusual kvn.

Change-Id: Icaf6f7b589f117868633c0968a99f2f0252cf612
2026-05-12 15:59:40 +02:00
Neels Hofmeyr cbfbe4ac40 personalization: implement UppAudit and BatchAudit
Change-Id: Iaab336ca91b483ecdddd5c6c8e08dc475dc6bd0a
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 8017025137 param_source: allow input val expansion like '0 * 32'
Working with keys, we often generate 4, 8, 16, 32 digit wide random
values. Those then typically have default input values like

 00000000000000000000000000000000

it is hard for humans to count the number of digits. Much easier:

 00*16

Teach the ParamSource subclasses dealing with random values to
understand an expansion like this. Any expansion is carried out before
all other input value handling.

Use this expansion also in the default_value of ConfigurableParameter
subclasses that have a default_source pointing at a ParamSource that now
understand this expansion.

Related: SYS#6768
Change-Id: Ie7171c152a7b478736f8825050305606b5af5735
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 0e9583f4f7 comment in uicc.py on Security Domain Keys: add SCP81
Change-Id: Ib0205880f58e78c07688b4637abd5f67ea0570d1
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 30fa174feb personalization: fix SdKey.apply_val() implementation
'securityDomain' elements are decoded to ProfileElementSD instances,
which keep higher level representations of the key data apart from the
decoded[] lists.

So far, apply_val() was dropping binary values in decoded[], which does
not work, because ProfileElementSD._pre_encode() overwrites
self.decoded[] from the higher level representation.

Implement using
- ProfileElementSD.find_key() and SecurityDomainKeyComponent to modify
  an exsiting entry, or
- ProfileElementSD.add_key() to create a new entry.

Before this patch, SdKey parameters seemed to patch PES successfully,
but their modifications did not end up in the encoded DER.

(BTW, this does not fix any other errors that may still be present in
the various SdKey subclasses, patches coming up.)

Related: SYS#6768
Change-Id: I07dfc378705eba1318e9e8652796cbde106c6a52
2026-05-12 15:59:40 +02:00
Neels Hofmeyr ef03270a93 personalization: add get_typical_input_len() to ConfigurableParameter
The aim is to tell a user interface how wide an input text field should
be chosen to be convenient -- ideally showing the entire value in all
cases, but not too huge for fields that have no sane size limit.

Change-Id: I2568a032167a10517d4d75d8076a747be6e21890
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 994fd13194 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
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 62f85218e8 personalization: indicate default ParamSource per ConfigurableParameter
Add default_source class members pointing to ParamSource classes to all
ConfigurableParameter subclasses.

This is useful to automatically set up a default ParamSource for a given
ConfigurableParameter subclass, during user interaction to produce a
batch personalization.

For example, if the user selects a Pin1 parameter, a calling program can
implicitly set this to a RandomDigitSource, which will magically make it
work the way that most users need.

BTW, default_source and default_value can be combined to configure a
matching ParamSource instance:

  my_source = MyParam.default_source.from_str( MyParam.default_value )

Change-Id: Ie58d13bce3fa1aa2547cf3cee918c2f5b30a8b32
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 1222ebe6ae personalization: allow reading back multiple values from PES
Change-Id: Iecb68af7c216c6b9dc3add469564416b6f37f7b2
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 9012d669b1 personalization: implement reading back values from a PES
Implement get_values_from_pes(), the reverse direction of apply_val():
read back and return values from a ProfileElementSequence. Implement for
all ConfigurableParameter subclasses.

Future: SdKey.get_values_from_pes() is reading pe.decoded[], which works
fine, but I07dfc378705eba1318e9e8652796cbde106c6a52 will change this
implementation to use the higher level ProfileElementSD members.

Implementation detail:

Implement get_values_from_pes() as classmethod that returns a generator.
Subclasses should yield all occurences of their parameter in a given
PES.

For example, the ICCID can appear in multiple places.
Iccid.get_values_from_pes() yields all of the individual values. A set()
of the results quickly tells whether the PES is consistent.

Rationales for reading back values:

This allows auditing an eSIM profile, particularly for producing an
output.csv from a batch personalization (that generated lots of random
key material which now needs to be fed to an HLR...).

Reading back from a binary result is more reliable than storing the
values that were fed into a personalization.
By auditing final DER results with this code, I discovered:
- "oh, there already was some key material in my UPP template."
- "all IMSIs ended up the same, forgot to set up the parameter."
- the SdKey.apply() implementations currently don't work, see
  I07dfc378705eba1318e9e8652796cbde106c6a52 for a fix.

Change-Id: I234fc4317f0bdc1a486f0cee4fa432c1dce9b463
2026-05-12 15:59:40 +02:00
Neels Hofmeyr b45c44e998 personalization: add param_source.py, add batch.py
Implement pySim.esim.saip.batch.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: I01ae40a06605eb205bfb409189fcd2b3a128855a
2026-05-12 15:59:40 +02:00
Neels Hofmeyr 04a47010d1 ts_31_102.py: EF_SUCI_Calc_Info(TransparentEF): fix len test
while len(foo):

throws an exception when foo == None.
Instead doing

    while foo:

fixes a problem when reading in empty SUCI calc info data, e.g. from
TS48v7.0_SAIP2.3_BERTLV_SUCI_NoRAMRFM.der.

Change-Id: Ia4e2356d0241d7a6ca399ba7e8be7f27ec836104
2026-05-12 15:59:40 +02:00
Philipp Maier 691b0d3c92 ts_51_011/EF.SMSP: fix handling of 'alpha_id' field
The field 'alpha_id' is technically not an optional field, even though
the specification describes it as optional. Once the card manufacturer
decides that the field should be present, it must be always present and
vice versa.

(see code comment for a more detailed description)

Related: SYS#7765
Change-Id: I0ec99b2648b22c56f9145345e4cd8776f9217701
2026-05-12 15:59:40 +02:00
Vadim Yanitskiy 03b58985a5 tests: pySim-smpp2sim_test.sh: fix copy-pasted comment
Change-Id: I8167c6a3251bb6755810c96075010e920ceee8ac
Jenkins: skip-card-test
2026-04-23 23:54:10 +07:00
Vadim Yanitskiy cc71dbf899 contrib/jenkins.sh: add setup_venv()
Reduece code duplication by factoring out virtualenv setup and
activation into a shell function.

Change-Id: Ibb193d12d5502c78104ef53badc6037f08e92df1
2026-04-23 23:54:00 +07:00
Vadim Yanitskiy aafc8d51c3 contrib/jenkins.sh: separate JOB_TYPE for card tests
A separate job gives us a possibility to skip tests requiring physical
cards for specific commits that do not touch the core logic.  See the
related commits in osmo-ci.git.

Change-Id: If76d812ee43b7eb3b57fdc660c60bf31fbff5b16
Related: osmo-ci.git Ia48d1b468f65d7c2e6b4128eeac36d0f3d03c45e
Related: osmo-ci.git I986d88545f64e13cd571ba9ff56bc924822e39a0
2026-04-23 23:52:55 +07:00
9 changed files with 779 additions and 1136 deletions
+20 -14
View File
@@ -10,6 +10,11 @@
export PYTHONUNBUFFERED=1
setup_venv() {
virtualenv -p python3 venv --system-site-packages
. venv/bin/activate
}
if [ ! -d "./tests/" ] ; then
echo "###############################################"
echo "Please call from pySim-prog top directory"
@@ -23,8 +28,7 @@ fi
case "$JOB_TYPE" in
"test")
virtualenv -p python3 venv --system-site-packages
. venv/bin/activate
setup_venv
pip install -r requirements.txt
pip install pyshark
@@ -32,23 +36,27 @@ case "$JOB_TYPE" in
# Execute automatically discovered unit tests first
python -m unittest discover -v -s tests/unittests
# Run pySim-prog integration tests (requires physical cards)
cd tests/pySim-prog_test/
./pySim-prog_test.sh
cd ../../
# Run pySim-trace test
tests/pySim-trace_test/pySim-trace_test.sh
;;
"card-test") # tests requiring physical cards
setup_venv
# Run pySim-shell integration tests (requires physical cards)
pip install -r requirements.txt
# Run pySim-prog integration tests
cd tests/pySim-prog_test/
./pySim-prog_test.sh
cd ../../
# Run pySim-shell integration tests
python3 -m unittest discover -v -s ./tests/pySim-shell_test/
# Run pySim-smpp2sim test
tests/pySim-smpp2sim_test/pySim-smpp2sim_test.sh
;;
"distcheck")
virtualenv -p python3 venv --system-site-packages
. venv/bin/activate
setup_venv
pip install .
pip install pyshark
@@ -61,8 +69,7 @@ case "$JOB_TYPE" in
# Print pylint version
pip3 freeze | grep pylint
virtualenv -p python3 venv --system-site-packages
. venv/bin/activate
setup_venv
pip install .
@@ -80,8 +87,7 @@ case "$JOB_TYPE" in
contrib/*.py
;;
"docs")
virtualenv -p python3 venv --system-site-packages
. venv/bin/activate
setup_venv
pip install -r requirements.txt
+59 -4
View File
@@ -34,7 +34,7 @@ from pySim import ts_102_222
from pySim.utils import dec_imsi
from pySim.ts_102_221 import FileDescriptor
from pySim.filesystem import CardADF, Path
from pySim.ts_31_102 import ADF_USIM
from pySim.ts_31_102 import ADF_USIM, EF_UST, EF_SUCI_Calc_Info
from pySim.ts_31_103 import ADF_ISIM
from pySim.esim import compile_asn1_subdir
from pySim.esim.saip import templates
@@ -1517,8 +1517,11 @@ class ProfileElementHeader(ProfileElement):
def mandatory_service_add(self, service_name):
self.decoded['eUICC-Mandatory-services'][service_name] = None
def mandatory_service_present(self, service_name):
return service_name in self.decoded['eUICC-Mandatory-services'].keys()
def mandatory_service_remove(self, service_name):
if service_name in self.decoded['eUICC-Mandatory-services'].keys():
if self.mandatory_service_present(service_name):
del self.decoded['eUICC-Mandatory-services'][service_name]
else:
raise ValueError("service not in eUICC-Mandatory-services list, cannot remove")
@@ -1726,12 +1729,64 @@ class ProfileElementSequence:
if 'BT' in ftype_list:
svc_set.add('ber-tlv')
# FIXME:dfLinked files (scan all files, check for non-empty Fcp.linkPath presence of DFs)
# TODO: 5G related bits (derive from EF.UST or file presence?)
# 5G:
# - When SUCI is:
# - enabled (EF.UST 124 = true)
# AND
# - calculated in the USIM (EF.UST 125 = true),
# then eUICC-Mandatory-services needs 'get-identity'.
# - 'get-identity' implies that the eUICC must support ONE OF profile-A OR profile-B.
# So, when SUCI-CalcInfo for USIM in DF.SAIP contains both key types,
# then no profile-A or B services need to be requested explicitly.
# - When the SUCI-CalcInfo for USIM (DF.SAIP) contains ONLY a key of profile-A ("identifier": 1),
# then eUICC-Mandatory-services needs 'profile-a-x25519'.
# - Same: ONLY profile-B ("identifier": 2) needs 'profile-b-p256'.
# - (When SUCI is calculated in the UE, then the eUICC does not need to provide any of these services.)
suci_in_usim_enabled = False
try:
f_ust = self.get_pe_for_type("usim").files["ef-ust"]
ust = EF_UST().decode_bin(f_ust.body)
suci_in_usim_enabled = ust[124]['activated'] and ust[125]['activated']
except (KeyError, AttributeError):
pass
if suci_in_usim_enabled:
svc_set.add('get-identity')
# now check for profile-a and profile-b
suci_calcinfo_has_profile_a = False
suci_calcinfo_has_profile_b = False
try:
f_sucici = self.get_pe_for_type("df-saip").files["ef-suci-calc-info-usim"]
sucici = EF_SUCI_Calc_Info().decode_bin(f_sucici.body) or {}
for prot_scheme in sucici['prot_scheme_id_list']:
if not isinstance(prot_scheme, dict):
continue
ps_id = prot_scheme["identifier"]
if ps_id == 1:
suci_calcinfo_has_profile_a = True
elif ps_id == 2:
suci_calcinfo_has_profile_b = True
except (KeyError, AttributeError):
pass
if suci_calcinfo_has_profile_a and suci_calcinfo_has_profile_b:
# 'get-identity' implies that the eUICC supports one of the above. Do not require a specific one.
pass
elif suci_calcinfo_has_profile_a:
# The profile has only a profile-A key, so require that
svc_set.add('profile-a-x25519')
elif suci_calcinfo_has_profile_b:
# The profile has only a profile-B key, so require that
svc_set.add('profile-b-p256')
hdr_pe = self.get_pe_for_type('header')
# patch in the 'manual' services from the existing list:
old_svc_set = set()
for old_svc in hdr_pe.decoded['eUICC-Mandatory-services'].keys():
if old_svc in manual_services:
svc_set.add(old_svc)
old_svc_set.add(old_svc)
logger.debug(f"{svc_set=} + {old_svc_set=}")
svc_set = svc_set.union(old_svc_set)
logger.debug(f"{svc_set=}")
hdr_pe.decoded['eUICC-Mandatory-services'] = {x: None for x in svc_set}
def rebuild_mandatory_gfstelist(self):
+2
View File
@@ -126,6 +126,8 @@ class BatchPersonalization:
logger.error('during %s: %r', _func_(), e)
raise ValueError(f'{p.param.name} fed by {p.src.name}: {e!r}') from e
pes.rebuild_mandatory_services()
yield pes
+225 -11
View File
@@ -27,13 +27,19 @@ from construct.core import StreamError
from osmocom.tlv import camel_to_snake
from osmocom.utils import hexstr
from pySim.utils import enc_iccid, dec_iccid, enc_imsi, dec_imsi, h2b, b2h, rpad, sanitize_iccid
from pySim.ts_31_102 import EF_AD, EF_UST, EF_Routing_Indicator, EF_SUCI_Calc_Info
from pySim.ts_31_102 import EF_AD, EF_UST, EF_Routing_Indicator, EF_SUCI_Calc_Info, DF_USIM_5GS
from pySim.ts_51_011 import EF_SMSP
from pySim.esim.saip import param_source
from pySim.esim.saip import ProfileElement, ProfileElementSD, ProfileElementSequence
from pySim.esim.saip import ProfileElementHeader
from pySim.esim.saip import SecurityDomainKey, SecurityDomainKeyComponent
from pySim.global_platform import KeyUsageQualifier, KeyType
# optimization: instantiate class instance to get the fid only once.
file_path_df_5gs = bytes.fromhex(DF_USIM_5GS().fid)
fid_ri = bytes.fromhex(EF_Routing_Indicator().fid)
fid_sucici = bytes.fromhex(EF_SUCI_Calc_Info().fid)
def unrpad(s: hexstr, c='f') -> hexstr:
return hexstr(s.rstrip(c))
@@ -1157,7 +1163,7 @@ class MilenageRotationConstants(BinaryParam, AlgoConfig):
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
Provided as octet-string concatenation of all 5 constants. The default value by 3GPP is the concatenation
of::
00000000000000000000000000000000
@@ -1344,13 +1350,36 @@ class SuciCalcInfoParameter(ConfigurableParameter):
if not f_sucici:
continue
ef_sucici = EF_SUCI_Calc_Info()
f_sucici.body = ef_sucici.encode_bin(val)
body = ef_sucici.encode_bin(val)
# 0xff pad up to the existing file size, so that the underlying template doesn't come through
is_size = f_sucici.file_size
pad_n = is_size - len(body)
if pad_n > 0:
body = body + b'\xff' * pad_n
f_sucici.body = body
pe.file2pe(f_sucici)
@classmethod
def apply_val(cls, pes: ProfileElementSequence, val):
cls._apply_suci(pes, val, *cls.suci_calc_info_pe)
@staticmethod
def normalize_sucici(sucici:dict):
"""Normalize the CalcInfo dict so it can be json encoded:
convert bytes to hex strings."""
if not sucici:
sucici = {}
for hnet_pubkey in sucici.get('hnet_pubkey_list', ()):
val = hnet_pubkey['hnet_pubkey']
if isinstance(val, bytes):
val = b2h(val)
hnet_pubkey['hnet_pubkey'] = val
return sucici
@classmethod
def _get_suci(cls, pes: ProfileElementSequence, pe_type="df-5gs", pe_file="ef-suci-calc-info"):
for pe in pes.get_pes_for_type(pe_type):
@@ -1360,15 +1389,8 @@ class SuciCalcInfoParameter(ConfigurableParameter):
ef_sucici = EF_SUCI_Calc_Info()
sucici = ef_sucici.decode_bin(f_sucici.body)
if not sucici:
sucici = {}
# normalize to string (bytes cannot go into json)
for hnet_pubkey in sucici.get('hnet_pubkey_list', ()):
val = hnet_pubkey['hnet_pubkey']
if isinstance(val, bytes):
val = b2h(val)
hnet_pubkey['hnet_pubkey'] = val
sucici = cls.normalize_sucici(sucici)
yield { cls.name: json.dumps(sucici) }
@@ -1385,3 +1407,195 @@ class SuciCalcInfoUsim(SuciCalcInfoParameter):
"""SUCI Calculation Information as in section 4.4.11.8 of 3GPP TS 31.102, readable only by USIM (DF-SAIP)"""
name = '5G-SUCI-CalcInfo-USIM'
suci_calc_info_pe = SuciCalcInfoParameter.PE_IN_USIM
def gfm_find(pes: ProfileElementSequence, file_path:bytes, ef_fid:bytes):
"""look through genericFileManagement PE and return the fmc list with start and end indexes as
(fmc_list, first_idx, after_last_idx)
so that fmc_list[first_idx:after_last_idx] is the slice of file management commands relevant to the given
file_path/ef_fid.
"""
for pe in pes.get_pes_for_type('genericFileManagement'):
path_match = False
creating_fid = False
for fmc in pe.decoded['fileManagementCMD']:
first = None
last = None
for idx in range(len(fmc)):
cmd, arg = fmc[idx]
if cmd == 'filePath':
path_match = (arg == file_path)
if not path_match:
creating_fid = False
elif path_match and cmd == 'createFCP':
creating_fid = (arg.get('fileID') == ef_fid)
if creating_fid:
if first is None:
first = idx
last = idx
first = min(first, idx)
last = max(last, idx)
if first is not None:
yield fmc, first, last + 1
# genericFileManagement 5G params
def pes_get_adf_fid(pes:ProfileElementSequence, naa_name="usim", adf_name="adf-usim"):
adf = pes.get_pe_for_type(naa_name)
return adf.decoded[adf_name][0][1]['fileID']
def mk_adf_df_path(pes, naa:str, adf:str, file_path:bytes) -> bytes:
adf_file_id = pes_get_adf_fid(pes, naa, adf)
return b''.join((adf_file_id, file_path))
def gfm_get_file_content(pes: ProfileElementSequence, naa:str, adf:str, file_path:bytes, ef_fid:bytes) -> bytes:
'''find a given file in the genericFileManagement section, and return the bytes from the first fillFileContent
item.
TODO: implement File.from_gfm() and return the full resulting bytes?
'''
adf_df_path = mk_adf_df_path(pes, naa, adf, file_path)
data = []
for fmc, first_idx, after_last_idx in gfm_find(pes, adf_df_path, ef_fid):
assert fmc[first_idx][0] == 'createFCP'
assert after_last_idx > first_idx
idx = first_idx + 1
while idx < after_last_idx:
if fmc[idx][0] == 'fillFileContent':
data.append(fmc[idx][1])
idx += 1
return data
def gfm_set_file_content(pes: ProfileElementSequence, naa:str, adf:str, file_path:bytes, ef_fid:bytes, file_content:bytes) -> int:
adf_df_path = mk_adf_df_path(pes, naa, adf, file_path)
found = 0
for fmc, first_idx, after_last_idx in gfm_find(pes, adf_df_path, ef_fid):
assert fmc[first_idx][0] == 'createFCP'
assert after_last_idx > first_idx
new_fmc = [
fmc[first_idx],
('fillFileContent', file_content),
]
new_fmc[0][1]['efFileSize'] = bytes((len(file_content), ))
fmc[first_idx:after_last_idx] = new_fmc
found += 1
return found
class GfmSuciRi(SuciRi):
"""SUCI Routing Indicator as in section 4.4.11.11 of 3GPP TS 31.102,
applied via General File Management. Intended for SAIP 2.1 profiles."""
name = 'GFM-5G-SUCI-RI'
@classmethod
def apply_val(cls, pes: ProfileElementSequence, val):
ri = {
"routing_indicator": str(val),
"rfu": "ffff"
}
ef_ri = EF_Routing_Indicator()
found = gfm_set_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_ri,
ef_ri.encode_bin(ri))
if not found:
raise ValueError(f"No target file found, Cannot apply {cls.name} = {ri}")
data = gfm_get_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_ri)
val = ef_ri.decode_bin(b''.join(data))
@classmethod
def get_values_from_pes(cls, pes: ProfileElementSequence):
data = gfm_get_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_ri)
if not data:
return
data = b''.join(data)
if not data:
return
ef_ri = EF_Routing_Indicator()
ri = ef_ri.decode_bin(data)
yield { cls.name: ri.get(cls.KEY_RI) }
class GfmSuciCalcInfoUe(SuciCalcInfoUe):
"""SUCI Calculation Information as in section 4.4.11.8 of 3GPP TS 31.102, readable by UE (DF-5GS),
applied via General File Management. Intended for SAIP 2.1 profiles."""
name = 'GFM-5G-SUCI-CalcInfo-UE'
@classmethod
def apply_val(cls, pes: ProfileElementSequence, val):
if not isinstance(val, dict):
raise ValueError("val should be a dict, after 'val = SuciCalcInfoParameter.validate_val(val)'")
ef_sucici = EF_SUCI_Calc_Info()
body = ef_sucici.encode_bin(val)
gfm_set_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_sucici,
body)
@classmethod
def get_values_from_pes(cls, pes: ProfileElementSequence):
data = gfm_get_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_sucici)
if not data:
return
data = b''.join(data)
if not data:
return
ef_sucici = EF_SUCI_Calc_Info()
sucici = ef_sucici.decode_bin(data)
sucici = cls.normalize_sucici(sucici)
yield { cls.name: json.dumps(sucici) }
class EuiccMandatoryServiceParam(EnumParam):
"""superclass for managing items of the ProfileHeader / eUICC-Mandatory-services ServicesList"""
service_name = None
value_map = { 'mandatory': True, 'optional': False }
default_source = param_source.ConstantSource
example_input = sorted(value_map.keys())[0]
@classmethod
def apply_val(cls, pes: ProfileElementSequence, val):
for pe in pes.get_pes_for_type('header'):
assert isinstance(pe, ProfileElementHeader)
if val:
pe.mandatory_service_add(cls.service_name)
else:
# explicitly check to avoid exception when then service is already not present
if pe.mandatory_service_present(cls.service_name):
pe.mandatory_service_remove(cls.service_name)
@classmethod
def get_values_from_pes(cls, pes: ProfileElementSequence):
for pe in pes.get_pes_for_type('header'):
assert isinstance(pe, ProfileElementHeader)
val = bool(pe.mandatory_service_present(cls.service_name))
yield { cls.name: cls.map_val_to_name(val) }
class EuiccMandatoryServiceGetIdentity(EuiccMandatoryServiceParam):
"""eUICC Mandatory Services: get-identity. The eUICC must be capable of providing a 5G identity using SUCI-CalcInfo
located in the USIM's DF-SAIP, see parameter 5G-SUCI-CalcInfo-USIM."""
name = '5G-eUICC-get-identity'
service_name = 'get-identity'
class EuiccMandatoryServiceProfileA(EuiccMandatoryServiceParam):
"""eUICC Mandatory Services: profile-a-x25519. The eUICC must be able to estblish a 5G identity using an X25519 key,
as provided in a profile-A ("identifier": 1) key in SUCI-CalcInfo located in the USIM's DF-SAIP, see parameter
5G-SUCI-CalcInfo-USIM."""
name = '5G-eUICC-profile-a-x25519'
service_name = 'profile-a-x25519'
class EuiccMandatoryServiceProfileB(EuiccMandatoryServiceParam):
"""eUICC Mandatory Services: profile-b-p256. The eUICC must be able to estblish a 5G identity using a P256 key, as
provided in a profile-B ("identifier": 2) key in SUCI-CalcInfo located in the USIM's DF-SAIP, see parameter
5G-SUCI-CalcInfo-USIM."""
name = '5G-eUICC-profile-b-p256'
service_name = 'profile-b-p256'
+31 -1
View File
@@ -251,6 +251,16 @@ class EF_SMSP(LinFixedEF):
"numbering_plan_id": "isdn_e164" },
"call_number": "4915790109999" },
"tp_pid": b"\x00", "tp_dcs": b"\x00", "tp_vp_minutes": 4320 } ),
( 'e1ffffffffffffffffffffffff0891945197109099f9ffffff0000a9',
{ "alpha_id": "", "parameter_indicators": { "tp_dest_addr": False, "tp_sc_addr": True,
"tp_pid": True, "tp_dcs": True, "tp_vp": True },
"tp_dest_addr": { "length": 255, "ton_npi": { "ext": True, "type_of_number": "reserved_for_extension",
"numbering_plan_id": "reserved_for_extension" },
"call_number": "" },
"tp_sc_addr": { "length": 8, "ton_npi": { "ext": True, "type_of_number": "international",
"numbering_plan_id": "isdn_e164" },
"call_number": "4915790109999" },
"tp_pid": b"\x00", "tp_dcs": b"\x00", "tp_vp_minutes": 4320 } ),
( '454e6574776f726b73fffffffffffffff1ffffffffffffffffffffffffffffffffffffffffffffffff0000a7',
{ "alpha_id": "ENetworks", "parameter_indicators": { "tp_dest_addr": False, "tp_sc_addr": True,
"tp_pid": True, "tp_dcs": True, "tp_vp": False },
@@ -331,7 +341,8 @@ class EF_SMSP(LinFixedEF):
'ton_npi'/TonNpi, 'call_number'/PaddedBcdAdapter(Rpad(Bytes(10))))
DestAddr = Struct('length'/Rebuild(Int8ub, lambda ctx: EF_SMSP.dest_addr_len(ctx)),
'ton_npi'/TonNpi, 'call_number'/PaddedBcdAdapter(Rpad(Bytes(10))))
self._construct = Struct('alpha_id'/COptional(GsmOrUcs2Adapter(Rpad(Bytes(this._.total_len-28)))),
# (see comment below)
self._construct = Struct('alpha_id'/GsmOrUcs2Adapter(Rpad(Bytes(this._.total_len-28))),
'parameter_indicators'/InvertAdapter(BitStruct(
Const(7, BitsInteger(3)),
'tp_vp'/Flag,
@@ -345,6 +356,25 @@ class EF_SMSP(LinFixedEF):
'tp_dcs'/Bytes(1),
'tp_vp_minutes'/EF_SMSP.ValidityPeriodAdapter(Byte))
# Ensure 'alpha_id' is always present
def encode_record_hex(self, abstract_data: dict, record_nr: int, total_len: int = None) -> str:
# Problem: TS 51.011 Section 10.5.6 describes the 'alpha_id' field as optional. However, this is only true
# at the time when the record length of the file is set up in the file system. A card manufacturer may decide
# to remove the field by setting the record length to 28. Likewise, the card manaufacturer may also decide to
# set the field to a distinct length by setting the record length to a value greater than 28 (e.g. 14 bytes
# 'alpha_id' + 28 bytes). Due to the fixed nature of the record length, this eventually means that in practice
# 'alpha_id' is a mandatory field with a fixed length.
#
# Due to the problematic specification of 'alpha_id' as a pseudo-optional field at the beginning of a
# fixed-size memory, the construct definition in self._construct has been incorrectly implemented and the field
# has been marked as COptional. We may correct the problem by removing COptional. But to maintain compatibility,
# we then have to ensure that in case the field is not provided (None), it is set to an empty string ('').
#
# See also ts_31_102.py, class EF_OCI for a correct example.
if abstract_data['alpha_id'] is None:
abstract_data['alpha_id'] = ''
return super().encode_record_hex(abstract_data, record_nr, total_len)
# TS 51.011 Section 10.5.7
class EF_SMSS(TransparentEF):
class MemCapAdapter(Adapter):
Binary file not shown.
@@ -1,6 +1,6 @@
#!/bin/bash
# Utility to verify the functionality of pySim-trace.py
# Utility to verify the functionality of pySim-smpp2sim.py
#
# (C) 2026 by sysmocom - s.f.m.c. GmbH
# All Rights Reserved
@@ -54,8 +54,7 @@ class ConfigurableParameterTest(unittest.TestCase):
def test_parameters(self):
upp_fnames = (
'TS48v5_SAIP2.1A_NoBERTLV.der',
'TS48v5_SAIP2.3_BERTLV_SUCI.der',
'SAIP2.1_gfmsuci.der',
'TS48v5_SAIP2.1B_NoBERTLV.der',
'TS48v5_SAIP2.3_NoBERTLV.der',
)
@@ -281,6 +280,33 @@ class ConfigurableParameterTest(unittest.TestCase):
val=3,
expect_clean_val=3,
expect_val='3'),
Paramtest(param_cls=p13n.EuiccMandatoryServiceGetIdentity,
val='mandatory',
expect_clean_val=True,
expect_val='mandatory'),
Paramtest(param_cls=p13n.EuiccMandatoryServiceGetIdentity,
val='optional',
expect_clean_val=False,
expect_val='optional'),
Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileA,
val='mandatory',
expect_clean_val=True,
expect_val='mandatory'),
Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileA,
val='optional',
expect_clean_val=False,
expect_val='optional'),
Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileB,
val='mandatory',
expect_clean_val=True,
expect_val='mandatory'),
Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileB,
val='optional',
expect_clean_val=False,
expect_val='optional'),
]
Paramtest.iff_present_default = True
@@ -323,10 +349,30 @@ class ConfigurableParameterTest(unittest.TestCase):
expect_clean_val='9999',
expect_val={'5G-SUCI-RI': '9999'}),
Paramtest(param_cls=p13n.SuciCalcInfo,
Paramtest(param_cls=p13n.SuciCalcInfoUe,
val=json.dumps(sucici),
expect_clean_val=sucici,
expect_val={'5G-SUCI-CalcInfo': json.dumps(sucici)}),
expect_val={'5G-SUCI-CalcInfo-UE': json.dumps(sucici)}),
Paramtest(param_cls=p13n.SuciCalcInfoUsim,
val=json.dumps(sucici),
expect_clean_val=sucici,
expect_val={'5G-SUCI-CalcInfo-USIM': json.dumps(sucici)}),
Paramtest(param_cls=p13n.GfmSuciRi, val='123',
expect_clean_val='123',
expect_val={'GFM-5G-SUCI-RI': '123'}),
Paramtest(param_cls=p13n.GfmSuciRi, val='0',
expect_clean_val='0',
expect_val={'GFM-5G-SUCI-RI': '0'}),
Paramtest(param_cls=p13n.GfmSuciRi, val='9999',
expect_clean_val='9999',
expect_val={'GFM-5G-SUCI-RI': '9999'}),
Paramtest(param_cls=p13n.GfmSuciCalcInfoUe,
val=json.dumps(sucici),
expect_clean_val=sucici,
expect_val={'GFM-5G-SUCI-CalcInfo-UE': json.dumps(sucici)}),
])
File diff suppressed because it is too large Load Diff