From a1892094f4ec881d8f2635dc57c9e68f29036dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 26 Oct 2026 00:01:23 +0300 Subject: [PATCH] uicc_files: supplement optional files missing from pySim's model Add a supplement to the cardless registry generator for the 13 files the Services editor's FS check references but pySim does not emit (optional, service-gated): EF.LRPLMNSI/IAL/IPD/3GPPPSDATAOFF/TVCONFIG, DF.MExE + EF.MExE-ST/ORPK/ARPK/TPRPK, DF.SoLSA + EF.SAI/EF.SLL, DF.BCAST, DF.ACDC + EF.ACDC_LIST, DF.TV, and DF.A2X + EF.AST/EF.A2X_CONFIG (under DF.TELECOM). Emitted with distinct (name, path) so same-FID files in other DFs (EF.SAI vs EF.PBR, EF.AST) stay separate; ADF.USIM rows are mirrored to ADF.USIM-NON-IMSI. 490 -> 527 entries. Regenerated the asset; test_uicc_files.py pins the 20 new paths, the FID-collision cases and (name, root) uniqueness. 1091 Python tests. --- frontend/uicc_files.json | 298 +++++++++++++++++++++++++++++- pysim_simple_server/uicc_files.py | 63 +++++++ tests/test_uicc_files.py | 54 ++++++ 3 files changed, 414 insertions(+), 1 deletion(-) diff --git a/frontend/uicc_files.json b/frontend/uicc_files.json index 1862567..5d17071 100644 --- a/frontend/uicc_files.json +++ b/frontend/uicc_files.json @@ -1,5 +1,5 @@ { - "generated": "2026-09-23", + "generated": "2026-10-25", "files": [ { "path": "MF/2F00", @@ -161,6 +161,30 @@ "kind": "ef", "root": "MF" }, + { + "path": "MF/7F10/5F3F", + "name": "DF.A2X", + "sym": "MF/DF.TELECOM/DF.A2X", + "fid": "5F3F", + "kind": "df", + "root": "MF" + }, + { + "path": "MF/7F10/5F3F/4F01", + "name": "EF.AST", + "sym": "MF/DF.TELECOM/DF.A2X/EF.AST", + "fid": "4F01", + "kind": "ef", + "root": "MF" + }, + { + "path": "MF/7F10/5F3F/4F02", + "name": "EF.A2X_CONFIG", + "sym": "MF/DF.TELECOM/DF.A2X/EF.A2X_CONFIG", + "fid": "4F02", + "kind": "ef", + "root": "MF" + }, { "path": "MF/7F10/6F3A", "name": "EF.ADN", @@ -1030,6 +1054,46 @@ "kind": "ef", "root": "ADF.USIM" }, + { + "path": "ADF.USIM/5F3C", + "name": "DF.MExE", + "sym": "ADF.USIM/DF.MExE", + "fid": "5F3C", + "kind": "df", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5F3C/4F40", + "name": "EF.MExE-ST", + "sym": "ADF.USIM/DF.MExE/EF.MExE-ST", + "fid": "4F40", + "kind": "ef", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5F3C/4F41", + "name": "EF.ORPK", + "sym": "ADF.USIM/DF.MExE/EF.ORPK", + "fid": "4F41", + "kind": "ef", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5F3C/4F42", + "name": "EF.ARPK", + "sym": "ADF.USIM/DF.MExE/EF.ARPK", + "fid": "4F42", + "kind": "ef", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5F3C/4F43", + "name": "EF.TPRPK", + "sym": "ADF.USIM/DF.MExE/EF.TPRPK", + "fid": "4F43", + "kind": "ef", + "root": "ADF.USIM" + }, { "path": "ADF.USIM/5F40", "name": "DF.WLAN", @@ -1182,6 +1246,38 @@ "kind": "ef", "root": "ADF.USIM" }, + { + "path": "ADF.USIM/5F70", + "name": "DF.SoLSA", + "sym": "ADF.USIM/DF.SoLSA", + "fid": "5F70", + "kind": "df", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5F70/4F30", + "name": "EF.SAI", + "sym": "ADF.USIM/DF.SoLSA/EF.SAI", + "fid": "4F30", + "kind": "ef", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5F70/4F31", + "name": "EF.SLL", + "sym": "ADF.USIM/DF.SoLSA/EF.SLL", + "fid": "4F31", + "kind": "ef", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5F80", + "name": "DF.BCAST", + "sym": "ADF.USIM/DF.BCAST", + "fid": "5F80", + "kind": "df", + "root": "ADF.USIM" + }, { "path": "ADF.USIM/5F90", "name": "DF.ProSe", @@ -1302,6 +1398,30 @@ "kind": "ef", "root": "ADF.USIM" }, + { + "path": "ADF.USIM/5FA0", + "name": "DF.ACDC", + "sym": "ADF.USIM/DF.ACDC", + "fid": "5FA0", + "kind": "df", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5FA0/4F01", + "name": "EF.ACDC_LIST", + "sym": "ADF.USIM/DF.ACDC/EF.ACDC_LIST", + "fid": "4F01", + "kind": "ef", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/5FB0", + "name": "DF.TV", + "sym": "ADF.USIM/DF.TV", + "fid": "5FB0", + "kind": "df", + "root": "ADF.USIM" + }, { "path": "ADF.USIM/5FC0", "name": "DF.5GS", @@ -2262,6 +2382,14 @@ "kind": "ef", "root": "ADF.USIM" }, + { + "path": "ADF.USIM/6FDC", + "name": "EF.LRPLMNSI", + "sym": "ADF.USIM/EF.LRPLMNSI", + "fid": "6FDC", + "kind": "ef", + "root": "ADF.USIM" + }, { "path": "ADF.USIM/6FDD", "name": "EF.NAFKCA", @@ -2358,6 +2486,14 @@ "kind": "ef", "root": "ADF.USIM" }, + { + "path": "ADF.USIM/6FF0", + "name": "EF.IAL", + "sym": "ADF.USIM/EF.IAL", + "fid": "6FF0", + "kind": "ef", + "root": "ADF.USIM" + }, { "path": "ADF.USIM/6FF1", "name": "EF.IPS", @@ -2366,6 +2502,14 @@ "kind": "ef", "root": "ADF.USIM" }, + { + "path": "ADF.USIM/6FF2", + "name": "EF.IPD", + "sym": "ADF.USIM/EF.IPD", + "fid": "6FF2", + "kind": "ef", + "root": "ADF.USIM" + }, { "path": "ADF.USIM/6FF3", "name": "EF.ePDGId", @@ -2414,6 +2558,22 @@ "kind": "ef", "root": "ADF.USIM" }, + { + "path": "ADF.USIM/6FF9", + "name": "EF.3GPPPSDATAOFF", + "sym": "ADF.USIM/EF.3GPPPSDATAOFF", + "fid": "6FF9", + "kind": "ef", + "root": "ADF.USIM" + }, + { + "path": "ADF.USIM/6FFB", + "name": "EF.TVCONFIG", + "sym": "ADF.USIM/EF.TVCONFIG", + "fid": "6FFB", + "kind": "ef", + "root": "ADF.USIM" + }, { "path": "ADF.USIM/6FFC", "name": "EF.XCAPConfigData", @@ -2527,6 +2687,46 @@ "kind": "ef", "root": "ADF.USIM-NON-IMSI" }, + { + "path": "ADF.USIM-NON-IMSI/5F3C", + "name": "DF.MExE", + "sym": "ADF.USIM-non-IMSI/DF.MExE", + "fid": "5F3C", + "kind": "df", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5F3C/4F40", + "name": "EF.MExE-ST", + "sym": "ADF.USIM-non-IMSI/DF.MExE/EF.MExE-ST", + "fid": "4F40", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5F3C/4F41", + "name": "EF.ORPK", + "sym": "ADF.USIM-non-IMSI/DF.MExE/EF.ORPK", + "fid": "4F41", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5F3C/4F42", + "name": "EF.ARPK", + "sym": "ADF.USIM-non-IMSI/DF.MExE/EF.ARPK", + "fid": "4F42", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5F3C/4F43", + "name": "EF.TPRPK", + "sym": "ADF.USIM-non-IMSI/DF.MExE/EF.TPRPK", + "fid": "4F43", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, { "path": "ADF.USIM-NON-IMSI/5F40", "name": "DF.WLAN", @@ -2679,6 +2879,38 @@ "kind": "ef", "root": "ADF.USIM-NON-IMSI" }, + { + "path": "ADF.USIM-NON-IMSI/5F70", + "name": "DF.SoLSA", + "sym": "ADF.USIM-non-IMSI/DF.SoLSA", + "fid": "5F70", + "kind": "df", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5F70/4F30", + "name": "EF.SAI", + "sym": "ADF.USIM-non-IMSI/DF.SoLSA/EF.SAI", + "fid": "4F30", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5F70/4F31", + "name": "EF.SLL", + "sym": "ADF.USIM-non-IMSI/DF.SoLSA/EF.SLL", + "fid": "4F31", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5F80", + "name": "DF.BCAST", + "sym": "ADF.USIM-non-IMSI/DF.BCAST", + "fid": "5F80", + "kind": "df", + "root": "ADF.USIM-NON-IMSI" + }, { "path": "ADF.USIM-NON-IMSI/5F90", "name": "DF.ProSe", @@ -2799,6 +3031,30 @@ "kind": "ef", "root": "ADF.USIM-NON-IMSI" }, + { + "path": "ADF.USIM-NON-IMSI/5FA0", + "name": "DF.ACDC", + "sym": "ADF.USIM-non-IMSI/DF.ACDC", + "fid": "5FA0", + "kind": "df", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5FA0/4F01", + "name": "EF.ACDC_LIST", + "sym": "ADF.USIM-non-IMSI/DF.ACDC/EF.ACDC_LIST", + "fid": "4F01", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/5FB0", + "name": "DF.TV", + "sym": "ADF.USIM-non-IMSI/DF.TV", + "fid": "5FB0", + "kind": "df", + "root": "ADF.USIM-NON-IMSI" + }, { "path": "ADF.USIM-NON-IMSI/5FC0", "name": "DF.5GS", @@ -3751,6 +4007,14 @@ "kind": "ef", "root": "ADF.USIM-NON-IMSI" }, + { + "path": "ADF.USIM-NON-IMSI/6FDC", + "name": "EF.LRPLMNSI", + "sym": "ADF.USIM-non-IMSI/EF.LRPLMNSI", + "fid": "6FDC", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, { "path": "ADF.USIM-NON-IMSI/6FDD", "name": "EF.NAFKCA", @@ -3847,6 +4111,14 @@ "kind": "ef", "root": "ADF.USIM-NON-IMSI" }, + { + "path": "ADF.USIM-NON-IMSI/6FF0", + "name": "EF.IAL", + "sym": "ADF.USIM-non-IMSI/EF.IAL", + "fid": "6FF0", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, { "path": "ADF.USIM-NON-IMSI/6FF1", "name": "EF.IPS", @@ -3855,6 +4127,14 @@ "kind": "ef", "root": "ADF.USIM-NON-IMSI" }, + { + "path": "ADF.USIM-NON-IMSI/6FF2", + "name": "EF.IPD", + "sym": "ADF.USIM-non-IMSI/EF.IPD", + "fid": "6FF2", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, { "path": "ADF.USIM-NON-IMSI/6FF3", "name": "EF.ePDGId", @@ -3903,6 +4183,22 @@ "kind": "ef", "root": "ADF.USIM-NON-IMSI" }, + { + "path": "ADF.USIM-NON-IMSI/6FF9", + "name": "EF.3GPPPSDATAOFF", + "sym": "ADF.USIM-non-IMSI/EF.3GPPPSDATAOFF", + "fid": "6FF9", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, + { + "path": "ADF.USIM-NON-IMSI/6FFB", + "name": "EF.TVCONFIG", + "sym": "ADF.USIM-non-IMSI/EF.TVCONFIG", + "fid": "6FFB", + "kind": "ef", + "root": "ADF.USIM-NON-IMSI" + }, { "path": "ADF.USIM-NON-IMSI/6FFC", "name": "EF.XCAPConfigData", diff --git a/pysim_simple_server/uicc_files.py b/pysim_simple_server/uicc_files.py index 1534e77..ee40c75 100644 --- a/pysim_simple_server/uicc_files.py +++ b/pysim_simple_server/uicc_files.py @@ -11,6 +11,11 @@ classes - the same model pySim assembles at equip time: It ships as ``frontend/uicc_files.json`` so the SIM/USIM RFM SELECT picker has a file list offline (no card, no server); when a card is equipped the picker merges the live tree over it and hides probed-absent files. +The optional / service-gated files pySim's model does not carry (the SoLSA, +MExE, BCAST, ACDC, TV and A2X DFs, EF.LRPLMNSI, EF.IAL, EF.IPD, EF.TVCONFIG, +EF.3GPPPSDATAOFF) are appended from ``SUPPLEMENT`` with their spec locations - +the decoded-editing filesystem check resolves a file by its (name, root), so a +same-FID file under another DF must stay a distinct entry. ``tests/test_uicc_files.py`` regenerates the list and fails when pySim adds or renames files, so the asset cannot silently drift. @@ -55,6 +60,62 @@ def _entry(fid_path, sym_path, name, fid, kind, root, aid=None): return out +# Files the pySim model does not carry (optional / service-gated), with their +# spec locations: TS 31.102 4.2.86 (EF.LRPLMNSI), 4.2.100 (EF.IAL), 4.2.102 +# (EF.IPD), 4.2.108 (EF.TVCONFIG), 4.2.109 (EF.3GPPPSDATAOFF), 4.4.1 +# (DF.SoLSA), 4.4.4 (DF.MExE), 4.4.7 (DF.BCAST - its files are OMA BCAST, so +# the DF is listed alone), 4.4.9 (DF.ACDC), 4.4.10 (DF.TV - EF.TVUSD is a +# variable 4Fxx, omitted) and 4.6.6 (DF.A2X); the FID map is +# docs/uicc/UICC_FILES.md 4.13. Entries are (root, parent FID chain, fid, +# name); a DF is a 'DF.' name and precedes its children so the symbolic chain +# can be resolved. +SUPPLEMENT = ( + ('ADF.USIM', (), '6FDC', 'EF.LRPLMNSI'), + ('ADF.USIM', (), '6FF0', 'EF.IAL'), + ('ADF.USIM', (), '6FF2', 'EF.IPD'), + ('ADF.USIM', (), '6FF9', 'EF.3GPPPSDATAOFF'), + ('ADF.USIM', (), '6FFB', 'EF.TVCONFIG'), + ('ADF.USIM', (), '5F3C', 'DF.MExE'), + ('ADF.USIM', ('5F3C',), '4F40', 'EF.MExE-ST'), + ('ADF.USIM', ('5F3C',), '4F41', 'EF.ORPK'), + ('ADF.USIM', ('5F3C',), '4F42', 'EF.ARPK'), + ('ADF.USIM', ('5F3C',), '4F43', 'EF.TPRPK'), + ('ADF.USIM', (), '5F70', 'DF.SoLSA'), + ('ADF.USIM', ('5F70',), '4F30', 'EF.SAI'), + ('ADF.USIM', ('5F70',), '4F31', 'EF.SLL'), + ('ADF.USIM', (), '5F80', 'DF.BCAST'), + ('ADF.USIM', (), '5FA0', 'DF.ACDC'), + ('ADF.USIM', ('5FA0',), '4F01', 'EF.ACDC_LIST'), + ('ADF.USIM', (), '5FB0', 'DF.TV'), + ('MF', ('7F10',), '5F3F', 'DF.A2X'), + ('MF', ('7F10', '5F3F'), '4F01', 'EF.AST'), + ('MF', ('7F10', '5F3F'), '4F02', 'EF.A2X_CONFIG'), +) + +# The non-IMSI USIM application repeats the ADF.USIM files under its own root +# (the walk emits it as a separate ADF with the same file set). +USIM_ROOT = 'ADF.USIM' +USIM_MIRROR = 'ADF.USIM-NON-IMSI' + + +def _add_supplement(out, seen, sym_by_path): + """Append SUPPLEMENT, mirroring the ADF.USIM files under the non-IMSI root.""" + for root, parents, fid, name in SUPPLEMENT: + roots = [root] + if root == USIM_ROOT and USIM_MIRROR in seen: + roots.append(USIM_MIRROR) + for target in roots: + key = '/'.join((target,) + tuple(parents) + (fid,)).upper() + if key in seen: + continue + parent_key = '/'.join((target,) + tuple(parents)).upper() + sym = '%s/%s' % (sym_by_path.get(parent_key, parent_key), name) + seen.add(key) + out.append(_entry(key, sym, name, fid, + 'df' if name.startswith('DF.') else 'ef', target)) + sym_by_path[key] = sym + + def _walk(node, fid_path, sym_path, root, out, seen): """Depth-first walk of a DF/ADF subtree (children is a fid -> CardFile dict).""" for fid, child in sorted(node.children.items()): @@ -116,6 +177,8 @@ def build(): seen.add(key) out.append(_entry(key, adf.name, adf.name, '', 'adf', adf.name, app.aid)) _walk(adf, key, adf.name, adf.name, out, seen) + # The optional / service-gated files pySim's model does not carry. + _add_supplement(out, seen, {e['path']: e['sym'] for e in out}) # Stable order: MF first, then the ADFs alphabetically; DFs before their EFs # is implicit in the walk, paths sort consistently inside a root. out.sort(key=lambda e: (0 if e['root'] == 'MF' else 1, e['root'], e['path'])) diff --git a/tests/test_uicc_files.py b/tests/test_uicc_files.py index ef66ed8..63d84ad 100644 --- a/tests/test_uicc_files.py +++ b/tests/test_uicc_files.py @@ -72,6 +72,60 @@ class TestUiccFilesAsset(unittest.TestCase): self.assertRegex(f['fid'], r'^[0-9A-F]{4}$') self.assertTrue(f['name'].startswith(('EF.', 'DF.'))) + def test_optional_files_missing_from_the_pysim_model(self): + """The service-gated files pySim does not model (the SUPPLEMENT).""" + files = _load()['files'] + by_path = {f['path']: f for f in files} + expected = { + # TS 31.102 4.2.86 / 4.2.100 / 4.2.102 / 4.2.108 / 4.2.109 + 'ADF.USIM/6FDC': 'EF.LRPLMNSI', + 'ADF.USIM/6FF0': 'EF.IAL', + 'ADF.USIM/6FF2': 'EF.IPD', + 'ADF.USIM/6FF9': 'EF.3GPPPSDATAOFF', + 'ADF.USIM/6FFB': 'EF.TVCONFIG', + # 4.4.4 DF.MExE + 'ADF.USIM/5F3C': 'DF.MExE', + 'ADF.USIM/5F3C/4F40': 'EF.MExE-ST', + 'ADF.USIM/5F3C/4F41': 'EF.ORPK', + 'ADF.USIM/5F3C/4F42': 'EF.ARPK', + 'ADF.USIM/5F3C/4F43': 'EF.TPRPK', + # 4.4.1 DF.SoLSA + 'ADF.USIM/5F70': 'DF.SoLSA', + 'ADF.USIM/5F70/4F30': 'EF.SAI', + 'ADF.USIM/5F70/4F31': 'EF.SLL', + # 4.4.7 / 4.4.9 / 4.4.10 + 'ADF.USIM/5F80': 'DF.BCAST', + 'ADF.USIM/5FA0': 'DF.ACDC', + 'ADF.USIM/5FA0/4F01': 'EF.ACDC_LIST', + 'ADF.USIM/5FB0': 'DF.TV', + # 4.6.6 DF.A2X (DF.TELECOM) + 'MF/7F10/5F3F': 'DF.A2X', + 'MF/7F10/5F3F/4F01': 'EF.AST', + 'MF/7F10/5F3F/4F02': 'EF.A2X_CONFIG', + } + for path, name in expected.items(): + self.assertIn(path, by_path) + self.assertEqual(by_path[path]['name'], name) + self.assertEqual(by_path[path]['root'], path.split('/')[0]) + self.assertEqual(by_path[path]['kind'], + 'df' if name.startswith('DF.') else 'ef') + # The same FID under another DF stays a distinct entry (name, path). + self.assertEqual(by_path['ADF.USIM/5F3A/4F30']['name'], 'EF.PBR') + self.assertEqual(by_path['MF/7F10/5F3A/4F30']['name'], 'EF.PBR') + # The ADF.USIM files are mirrored under the non-IMSI USIM root. + for path, name in expected.items(): + if not path.startswith('ADF.USIM/'): + continue + mirror = 'ADF.USIM-NON-IMSI' + path[len('ADF.USIM'):] + self.assertIn(mirror, by_path) + self.assertEqual(by_path[mirror]['name'], name) + self.assertEqual(by_path[mirror]['root'], 'ADF.USIM-NON-IMSI') + # (name, root) resolves each SUPPLEMENT file to exactly one path. + for path, name in expected.items(): + root = path.split('/')[0] + self.assertEqual([f['path'] for f in files + if f['name'] == name and f['root'] == root], [path]) + if __name__ == '__main__': unittest.main()