feat: infer the ETSI release from the CAP's uicc.toolkit import (v3.6.44)

The CAP details' Platform row named only the Java Card level; it now also
shows the ETSI/3GPP release the UICC API imports come from, with its basis
in brackets, e.g. 'Java Card >= 2.2.2 - ETSI REL-7 (uicc.toolkit 1.4) -
CAP format 2.1'.

capmem.ETSI_TOOLKIT_RELEASE maps the uicc.toolkit export version to the
release, sourced from the workspace corpus: sim-tools/ETSI-REL.md for the
prepared trees (api21_rel7_export_files carries uicc.toolkit 1.4 = the
REL-7 jar, api21_export_files 1.11 = the REL-12 jar) and
sim-tools/etsi/102241/README.md's package-version table for the rest
(1.0 -> REL-6/7, 1.3 -> REL-8, 1.5 -> REL-9, 1.12-1.14 -> REL-15..18; the
1.11 spec range is 13.01-14.02 but the tree the builds use is REL-12).
Exposed as requires_etsi_release/requires_etsi_basis in /api/cap-info and
the CLI; SIM-only CAPs (no uicc.toolkit) get no label.

Verified: RemMobileID -> REL-7 (uicc.toolkit 1.4), AlfaDSA -> REL-12
(uicc.toolkit 1.11).  sw cache simple-v317; tests: 531 Python / 677 Node.
This commit is contained in:
2026-09-29 23:17:27 +03:00
parent f21d47538f
commit 3d4e4e81f8
9 changed files with 74 additions and 6 deletions
+22
View File
@@ -332,6 +332,28 @@ class TestCapAnalyzer(unittest.TestCase):
self.assertEqual(info['requires_framework'], '2.0')
self.assertIsNone(info['requires_java_card'])
def test_etsi_release_from_the_uicc_toolkit_import(self):
uicc = bytes.fromhex('A0000000090005FFFFFFFF8912000000')
# the REL-7 prepared tree carries uicc.toolkit 1.4
report, _ = capmem.analyze_bytes(build_cap(imports=[(4, 1, uicc)]))
info = capmem.memory_json(report, capmem.compute_memory(report))
self.assertEqual(info['requires_etsi_release'], 'REL-7')
self.assertEqual(info['requires_etsi_basis'], 'uicc.toolkit 1.4')
# the default (REL-12) tree carries 1.11
report, _ = capmem.analyze_bytes(build_cap(imports=[(11, 1, uicc)]))
info = capmem.memory_json(report, capmem.compute_memory(report))
self.assertEqual(info['requires_etsi_release'], 'REL-12')
# an unmapped version keeps the basis, without a release
report, _ = capmem.analyze_bytes(build_cap(imports=[(7, 1, uicc)]))
info = capmem.memory_json(report, capmem.compute_memory(report))
self.assertIsNone(info['requires_etsi_release'])
self.assertEqual(info['requires_etsi_basis'], 'uicc.toolkit 1.7')
# no uicc.toolkit import -> no ETSI line (SIM-only CAPs have no mapping)
report, _ = capmem.analyze_bytes(build_cap(imports=[(3, 1, JAVACARD_FRAMEWORK)]))
info = capmem.memory_json(report, capmem.compute_memory(report))
self.assertIsNone(info['requires_etsi_release'])
self.assertIsNone(info['requires_etsi_basis'])
def test_memory_json_nvram_requirement_uses_the_load_file(self):
report, memory = capmem.analyze_bytes(rich_cap())
info = capmem.memory_json(report, memory, load_file_bytes=1000)