diff --git a/docs/decoded-editing.md b/docs/decoded-editing.md
index 68ff209..63b43f5 100644
--- a/docs/decoded-editing.md
+++ b/docs/decoded-editing.md
@@ -300,14 +300,19 @@ de-allocates.
Each button writes only that file via `/api/write`; an unchanged file is
skipped and the panel reports each write's result (there is no atomic
cross-file write).
-- **Filesystem check**: many services declare a DF or EF (ADN -> EF.ADN 6F3A,
- SPN -> EF.SPN 6F46, GSM Access -> DF.GSM, GPRS -> EF.KcGPRS, …); a second
- curated table maps a feature to the file(s) it requires. A **Check** action
- probes the equipped card for those files (reusing the file-manager presence
- state / a targeted SELECT) and flags mismatches — a declared service whose
+- **Filesystem check** (implemented, step 4b): many services declare a DF or EF
+ (ADN -> EF.ADN 6F3A, SPN -> EF.SPN 6F46, GSM Access -> DF.GSM-ACCESS 5F3B,
+ GPRS -> EF.KcGPRS, …); a second curated table (`SERVICES_FS_MAP`, keyed by
+ feature name) maps a feature to the FID(s) its `Filesystem` cell declares.
+ A **Check filesystem** action writes the services, probes the file-manager
+ tree once (`pysimFsProbeAll`) and flags mismatches — a declared service whose
file is absent, or a present file whose service is not declared. Services
that map to a capability rather than a file (CHV1 disable, Proactive SIM,
- …) carry no file check. The result is cached per `card_session`.
+ …) carry no file check. This first pass matches **FID-level**, not
+ (DF, FID)-exact as finding 6 asks: the documented FID collisions are not
+ disambiguated and the report says so (`docs/services-editor.md` §B.0). The
+ last report is kept in memory and dropped with the cached reads on a
+ card-session change.
### 6.1 Candidates for the same cross-application treatment
diff --git a/docs/services-editor.md b/docs/services-editor.md
index 6d5dcaa..118ca09 100644
--- a/docs/services-editor.md
+++ b/docs/services-editor.md
@@ -9,8 +9,9 @@
>
> Step 4a (the cross-file Services editor: §A's cross-reference, read, render,
> per-file save) is implemented against §A; the counts below were verified
-> against the PWA's constants by `frontend/tests/services.test.js`. §B (the
-> filesystem check) is **not** implemented yet.
+> against the PWA's constants by `frontend/tests/services.test.js`. Step 4b
+> (§B, the filesystem check) is implemented as well - see §B.0 for the
+> representation and the FID-level rule it uses.
**Counts:** SST **59** / UST **150** / IST **22** / EST **3** services →
**164 feature rows** (10 SIM-only, 85 USIM-only, 5 ISIM-only, 61 shared,
@@ -132,7 +133,7 @@ Names are canonicalised; wording aliases are listed in Notes §1.
| 93 | Allowed CSG Lists and corresponding indications | | 86 | | | USIM-only | EF.ACSGL 4F81 · EF.CSGT 4F82 · EF.HNBN 4F83 |
| 94 | Call control on EPS PDN connection by USIM | | 87 | | | USIM-only | capability |
| 95 | HPLMN Direct Access | | 88 | | | USIM-only | EF.HPLMNDAI 4F4B |
-| 96 | eCall Data | | 89 | | | USIM-only | EF.SDN 6F49 (+ EF.FDN 6F3A) |
+| 96 | eCall Data | | 89 | | | USIM-only | EF.SDN 6F49 (+ EF.FDN 6F3B) |
| 97 | Operator CSG Lists and corresponding indications | | 90 | | | USIM-only | EF.OCSGL 4F84 · EF.OCSGT 4F85 · EF.OHNBN 4F86 |
| 98 | Support for SM-over-IP | | 91 | 8 | | shared | capability (ISIM-related: EF.PSISMSC 6FE5) |
| 99 | Support of CSG Display Control | | 92 | | | USIM-only | capability |
@@ -226,6 +227,49 @@ The FS column in §A is deliverable B, merged for readability. Legend:
- `?` — no clear file found in either source; needs a human decision.
- `—` — RFU/reserved.
+### B.0 Implemented check (`SERVICES_FS_MAP` + the Check filesystem action)
+
+`frontend/index.html` carries the column as `SERVICES_FS_MAP`, a map keyed by
+the §A feature name (the cross-reference's row identity) whose value is the
+list of **every FID the cell mentions**, in the cell's order:
+
+- `EF.FDN 6F3B (+ EF.EXT2 6F4B)` → `['6F3B', '6F4B']`
+- `DF.GRAPHICS 5F50 → EF.IMG 4F20` → `['5F50', '4F20']` (a DF's FID counts:
+ the DF itself is the file the service gates)
+- `EF.NIA 6F51 (SIM) / 6FD3 (USIM)` → both trees' FIDs
+- `capability` → `[]`; the parenthetical references a capability cell carries
+ (`gate: EF.CBMID 6F48`, `menu title: EF.SUME 6F54`, the URI set, `SUCI data`)
+ are informational — the service does not declare a file of its own. The
+ §A.4 RFU/reserved rows are not features and therefore have no entry.
+
+The **Check filesystem** button (Services pill, needs a card) reads the four
+service tables, probes the file manager's tree once (`pysimFsProbeAll`; the
+`/api/tree` model lists files without testing them, so a loaded-but-unprobed
+tree proves nothing), and compares the two sides:
+
+- `missing` — a **declared** feature (any of its service bits set in a present
+ file; EF.SST only at *allocated + activated*, the state the editor shows)
+ whose mapped FIDs are absent from the card. In §A order.
+- `extra` — a **present** FID that no declared feature maps. Sorted by FID,
+ naming every feature that maps it (all of them undeclared). A FID **any**
+ declared feature maps counts as explained and is not reported: several
+ features share one file (`4F01` under the ACDC/MCS/5GS/V2X/5G ProSe/5MBS/A2X
+ DFs, `6F4A`/`6F4B` across EXT1/EXT2 and BDN) and the rule cannot tell which
+ DF the present copy sits in.
+
+The check is **FID-level, not (DF, FID)-exact** (this first pass; finding 6 in
+`docs/decoded-editing.md` asks for DF-scoped resolution). The FID collisions
+of Notes §3 — `6F4E` EXT4/EXT5, `6F4F` ECCP/CCP2, `6F07` IMSI/IST, `6F38`
+SST/UST, `6FD7` MSK/GBANL, `5F3B` DF.GSM-ACCESS/DF.MULTIMEDIA — are therefore
+not disambiguated, and the report says so. `EF.PBR 4F30` also exists in
+several DFs, so a present `4F30` explains `ADN`/`SoLSA` regardless of which
+copy it is.
+
+The pure pieces (`servicesFsDeclared`, `servicesFsPresentFids`,
+`servicesFsReport`, `servicesFsReportHtml`) are DOM-free and unit-tested; the
+last report is kept in memory and redrawn on a language switch, and it is
+dropped with the cached reads on a card-session change.
+
### B.1 `capability` entries (no single file)
CHV1 disable · Data download via SMS-PP · Data download via SMS-CB (gate
@@ -251,7 +295,7 @@ The three initially-unresolved mappings were settled from the spec text
- **eCall Data (89)** — no dedicated EF: the eCall test/reconfiguration
numbers live in **EF.SDN 6F49** ("If service n° 4 and/or n° 89 is available
this file shall be present … eCall test and reconfiguration numbers") and
- EF.FDN 6F3A (the eCall procedures).
+ EF.FDN 6F3B (the eCall procedures).
- **eCall Data over IMS (112)** — the eCall test/reconfiguration **URIs** live
in **EF.SDNURI 6FEF** (and EF.FDNURI 6FED).
- **EARFCN list for MTC/NB-IOT UEs (121)** — **EF.EARFCNList 6FFD** (§4.2.112,
diff --git a/frontend/index.html b/frontend/index.html
index a26b0ac..ff05eec 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1176,6 +1176,13 @@
+
+
+
+
+
+
+
@@ -16068,6 +16075,7 @@ function pysimFsSetSort(mode) {
async function pysimFsRefresh() {
const detailEl = document.getElementById('pysim-fs-detail');
detailEl.classList.add('hidden');
+ _pysimFsProbed = false;
const data = await pysimFetch('/api/tree', { name: 'MF' });
pysimFsTreeRoot = {
name: data.name || 'MF',
@@ -16121,6 +16129,10 @@ async function pysimFsLoadChildren(node) {
}
let _pysimFsProbe = null;
+// Whether the tree's presence state comes from a completed probe (a refresh
+// resets it): /api/tree lists the model's files without testing them, so a
+// loaded tree alone says nothing about what the card actually has.
+let _pysimFsProbed = false;
function pysimFsSelectBody(node) {
const body = { name: node.name, fid: node.fid, parent_sel: getParentSel(node), parent_path: getParentPath(node) };
@@ -16196,6 +16208,7 @@ async function pysimFsProbeAll() {
+ probe.present + ' ' + t('present') + ', ' + probe.absent + ' ' + t('absent') + ' (' + secs + 's)';
if (error) summary = t('Error') + ': ' + error + ' — ' + summary;
else if (probe.stop) summary = t('Stopped') + ' — ' + summary;
+ _pysimFsProbed = !probe.stop && !error;
_pysimFsProbe = null;
pysimFsProbeUi(summary, false);
}
@@ -23288,6 +23301,192 @@ const SERVICES_FILES = [
const SERVICES_TABLES = { ust: EF_UST_SERVICES, est: EF_EST_SERVICES, ist: EF_IST_SERVICES };
+// Feature -> the FID(s) its Filesystem cell declares, transcribed from
+// docs/services-editor.md §A (the merged "Filesystem" column, legend in §B):
+// every FID the cell mentions, in order - `EF.FDN 6F3B (+ EF.EXT2 6F4B)`
+// -> ['6F3B', '6F4B'], `DF.GRAPHICS 5F50 -> EF.IMG 4F20` -> ['5F50', '4F20']
+// (a DF's FID counts: the DF itself is the file the service gates),
+// `EF.NIA 6F51 (SIM) / 6FD3 (USIM)` -> both trees' FIDs.
+// `capability` cells (procedural/terminal-side features with no file of their
+// own) map to [] - their parenthetical references (a gate, a title, a URI
+// set) are informational, not a file the service declares - and so do the
+// §A.4 RFU/reserved rows, which are not features and therefore absent here.
+// The map is keyed by feature name because that is the cross-reference's key
+// (the editor's row identity); SERVICES_FILES above is the four *service
+// table* descriptors, so the name is taken. Coverage is pinned by
+// frontend/tests/services.test.js.
+//
+// The check is deliberately FID-level, not (DF, FID)-exact (docs/decoded-
+// editing.md finding 6 asks for DF-scoped resolution; this first pass accepts
+// the coarser rule, visible to the user as the report's note): the known
+// collisions - 6F4E EXT4/EXT5, 6F4F ECCP/CCP2, 6F07 IMSI/IST, 6F38 SST/UST,
+// 6FD7 MSK/GBANL, 5F3B DF.GSM-ACCESS/DF.MULTIMEDIA - are matched by FID
+// anywhere in the card's tree, so a file carrying a colliding FID can explain
+// a service it does not actually gate.
+const SERVICES_FS_MAP = {
+ 'CHV1 disable function': [],
+ 'Local Phone Book (ADN)': ['6F3A', '5F3A', '4F30'],
+ 'Fixed Dialling Numbers (FDN)': ['6F3B', '6F4B'],
+ 'Short Message Storage (SMS)': ['6F3C'],
+ 'Advice of Charge (AoC)': ['6F39', '6F37', '6F41'],
+ 'Capability Configuration Parameters (CCP)': ['6F3D'],
+ 'Capability Configuration Parameters 2 (CCP2)': ['6F4F'],
+ 'PLMN selector': ['6F30'],
+ 'MSISDN': ['6F40'],
+ 'Extension 1': ['6F4A'],
+ 'Extension 2': ['6F4B'],
+ 'SMS Parameters (SMSP)': ['6F42'],
+ 'Last Number Dialled (LND)': ['6F44'],
+ 'Cell Broadcast Message Identifier (CBMI)': ['6F45'],
+ 'Group Identifier Level 1 (GID1)': ['6F3E'],
+ 'Group Identifier Level 2 (GID2)': ['6F3F'],
+ 'Service Provider Name (SPN)': ['6F46'],
+ 'Service Dialling Numbers (SDN)': ['6F49', '6F4C'],
+ 'Extension 3': ['6F4C'],
+ 'VGCS Group Identifier List': ['6FB1', '6FB2'],
+ 'VBS Group Identifier List': ['6FB3', '6FB4'],
+ 'enhanced Multi-Level Precedence and Pre-emption (eMLPP)': ['6FB5'],
+ 'Automatic Answer for eMLPP (AAeM)': ['6FB6'],
+ 'Data download via SMS-CB': [],
+ 'Data download via SMS-PP': [],
+ 'Menu selection': [],
+ 'Call control (by SIM/USIM)': [],
+ 'Proactive SIM': [],
+ 'Cell Broadcast Message Identifier Ranges (CBMIR)': ['6F50'],
+ 'Barred Dialling Numbers (BDN)': ['6F4D', '6F4E', '6F55'],
+ 'Extension 4': ['6F4E', '6F55'],
+ 'De-personalisation Control Keys (DCK)': ['6F2C'],
+ 'Co-operative Network List (CNL)': ['6F32'],
+ 'Short Message Status Reports (SMSR)': ['6F47'],
+ 'Network\'s indication of alerting in the MS (NIA)': ['6F51', '6FD3'],
+ 'MO-SMS control (by SIM/USIM)': [],
+ 'GPRS': ['6F52', '7F20'],
+ 'Image (IMG)': ['5F50', '4F20'],
+ 'SoLSA': ['5F70', '4F30', '4F31'],
+ 'USSD string data object supported in Call Control': [],
+ 'RUN AT COMMAND command': [],
+ 'User controlled PLMN selector with Access Technology': ['6F60'],
+ 'Operator controlled PLMN selector with Access Technology': ['6F61'],
+ 'HPLMN selector with Access Technology': ['6F62'],
+ 'CPBCCH Information': ['6F63', '4F63'],
+ 'Investigation Scan': ['6F64', '4F64'],
+ 'Extended Capability Configuration Parameters (ECCP)': ['6F4F'],
+ 'MExE': ['5F3C', '4F40'],
+ 'PLMN Network Name (PNN)': ['6FC5'],
+ 'Operator PLMN List (OPL)': ['6FC6'],
+ 'Mailbox Dialling Numbers (MBDN)': ['6FC7', '6FC8'],
+ 'Message Waiting Indication Status (MWIS)': ['6FCA', '6FC9'],
+ 'Call Forwarding Indication Status (CFIS)': ['6FCB', '6FCC'],
+ 'Service Provider Display Information (SPDI)': ['6FCD'],
+ 'Multimedia Messaging Service (MMS)': ['6FCE'],
+ 'Extension 8': ['6FCF'],
+ 'MMS User Connectivity Parameters': ['6FD0', '6FD2', '6FD1'],
+ 'Outgoing Call Information (OCI and OCT)': ['6F81', '6F83'],
+ 'Incoming Call Information (ICI and ICT)': ['6F80', '6F82'],
+ 'GSM Access': ['5F3B'],
+ 'Enabled Services Table': ['6F56'],
+ 'APN Control List (ACL)': ['6F57'],
+ 'GSM security context': ['4F20', '4F52', '5F3B'],
+ 'Extension 5': ['6F4E'],
+ 'Call control on GPRS by USIM': [],
+ 'Pseudonym': ['4F41', '5F40'],
+ 'User Controlled PLMN selector for I-WLAN access': ['4F42'],
+ 'Operator Controlled PLMN selector for I-WLAN access': ['4F43'],
+ 'User controlled WSID list': ['4F44'],
+ 'Operator controlled WSID list': ['4F45'],
+ 'VGCS security': ['6FD4'],
+ 'VBS security': ['6FD5'],
+ 'WLAN Reauthentication Identity': ['4F46'],
+ 'Multimedia Messages Storage': ['4F47', '4F48', '5F3B'],
+ 'Generic Bootstrapping Architecture (GBA)': ['6FD6', '6FD5', '6FDA', '6FD7', '6FDD'],
+ 'MBMS security': ['6FD7', '6FD8'],
+ 'Data download via USSD and USSD application mode': [],
+ 'Equivalent HPLMN': ['6FD9'],
+ 'Additional TERMINAL PROFILE after UICC activation': [],
+ 'Equivalent HPLMN Presentation Indication': ['6FDB'],
+ 'Last RPLMN Selection Indication': ['6FDC'],
+ 'OMA BCAST Smart Card Profile': ['5F80'],
+ 'GBA-based Local Key Establishment Mechanism': [],
+ 'Terminal Applications': [],
+ 'Service Provider Name Icon': ['6FDE'],
+ 'PLMN Network Name Icon': ['6FDF'],
+ 'Connectivity Parameters for USIM IP connections': ['6FE2'],
+ 'Home I-WLAN Specific Identifier List': ['4F47'],
+ 'I-WLAN Equivalent HPLMN Presentation Indication': ['4F48'],
+ 'I-WLAN HPLMN Priority Indication': ['4F49'],
+ 'I-WLAN Last Registered PLMN': ['4F4A'],
+ 'EPS Mobility Management Information': ['6FE3', '6FE4'],
+ 'Allowed CSG Lists and corresponding indications': ['4F81', '4F82', '4F83'],
+ 'Call control on EPS PDN connection by USIM': [],
+ 'HPLMN Direct Access': ['4F4B'],
+ 'eCall Data': ['6F49', '6F3B'],
+ 'Operator CSG Lists and corresponding indications': ['4F84', '4F85', '4F86'],
+ 'Support for SM-over-IP': [],
+ 'Support of CSG Display Control': [],
+ 'Communication Control for IMS': [],
+ 'Extended Terminal Applications': [],
+ 'Support of UICC access to IMS': [],
+ 'Non-Access Stratum configuration by USIM': ['6FE8'],
+ 'PWS configuration by USIM': ['6FEC'],
+ 'URI support by UICC': [],
+ 'Extended EARFCN support': [],
+ 'ProSe': ['5F90', '4F10'],
+ 'USAT Application Pairing': ['6FF1', '6FF0', '6FF2'],
+ 'Media Type support': [],
+ 'IMS call disconnection cause': [],
+ 'URI support for MO SHORT MESSAGE CONTROL': [],
+ 'ePDG configuration Information support': ['6FF4'],
+ 'ePDG configuration Information configured': ['6FF3'],
+ 'ACDC support': ['5FA0', '4F01'],
+ 'MCPTT': ['5F3D', '4F01'],
+ 'ePDG configuration Information for Emergency Service support': ['6FF6'],
+ 'ePDG configuration Information for Emergency Service configured': ['6FF5'],
+ 'eCall Data over IMS': ['6FEF', '6FED'],
+ 'URI support for SMS-PP DOWNLOAD': [],
+ 'From Preferred': ['6FF7'],
+ 'IMS configuration data': ['6FF8'],
+ 'TV configuration': ['6FFB', '5FB0'],
+ '3GPP PS Data Off': ['6FF9'],
+ '3GPP PS Data Off Service List': ['6FFA'],
+ 'V2X': ['5F3E', '4F01', '4F02'],
+ 'XCAP Configuration Data': ['6FFC'],
+ 'EARFCN list for MTC/NB-IOT UEs': ['6FFD'],
+ '5GS Mobility Management Information': ['5FC0', '4F01', '4F02'],
+ '5G Security Parameters': ['4F05', '4F03', '4F04'],
+ 'Subscription identifier privacy support': [],
+ 'SUCI calculation by the USIM': ['4F07'],
+ 'UAC Access Identities support': ['4F06'],
+ 'Expect control plane-based Steering of Roaming information during initial registration in VPLMN': [],
+ 'Call control on PDU Session by USIM': [],
+ '5GS Operator PLMN List': ['4F08'],
+ 'Support for SUPI of type NSI or GLI or GCI': ['4F09'],
+ '3GPP PS Data Off separate Home and Roaming lists': [],
+ 'Support for URSP by USIM': ['4F0B'],
+ '5G Security Parameters extended': [],
+ 'MuD and MiD configuration data': ['6FFE'],
+ 'Support for Trusted non-3GPP access networks by USIM': [],
+ 'Support for multiple records of NAS security context storage for multiple registration': ['4F03', '4F04'],
+ 'Pre-configured CAG information list': ['4F0D'],
+ 'SOR-CMCI storage in USIM': ['4F0E'],
+ '5G ProSe': ['5FF0', '4F01'],
+ 'Storage of disaster roaming information in USIM': ['4F0F'],
+ 'Pre-configured eDRX parameters': ['4F10'],
+ '5G NSWO support': ['4F11'],
+ 'PWS configuration for SNPN in USIM': ['4F01', '5FE0'],
+ 'Multiplier Coefficient for Higher Priority PLMN search via NG-RAN satellite access': ['4F15'],
+ 'K_AUSF derivation configuration': ['4F16'],
+ 'Network Identifier for SNPN (NID)': ['4F02', '5FE0'],
+ '5MBS UE pre-configuration': ['5FF1', '4F01'],
+ 'Operator Controlled Signal Threshold per Access Technology': ['6F02'],
+ 'A2X': ['5F3F', '4F01'],
+ 'IMS Data Channel Indication': ['6F0B'],
+ 'P-CSCF address': ['6F09'],
+ 'HTTP Digest': [],
+ 'Support of P-CSCF discovery for IMS Local Break Out': [],
+ 'WebRTC URI': ['6FFA'],
+ 'IMS Data Channel indication': ['6F0B'],
+};
+
function servicesDecode(key, bytes) {
if (key === 'sst') return efDecSst(bytes);
return efDecServiceTable(bytes, SERVICES_TABLES[key]);
@@ -23406,11 +23605,143 @@ function servicesFileBytes(key, values, files) {
return efEncServiceTable(servicesTableValues(values, key, f.hex), f.hex, table);
}
+// --- filesystem check (docs/services-editor.md §B) ---
+// Three pure pieces: which features the card declares (from the same decoded
+// service tables the grid renders), which FIDs the card actually has (from
+// the file manager's probed tree) and the mismatch report between the two.
+
+// Feature names whose service bit is set in at least one of the present
+// service files - the "declared" side of the check. A file that is absent
+// from the card leaves its column null (servicesFeatureState), so it declares
+// nothing; EF.SST counts only at 'on' (allocated + activated), as the editor
+// renders it.
+function servicesFsDeclared(files) {
+ const dec = key => (files && files[key] && files[key].present) ? files[key].dec : null;
+ const sstDec = dec('sst'), ustDec = dec('ust'), estDec = dec('est'), istDec = dec('ist');
+ const out = [];
+ for (const f of SERVICES_CROSS_REF) {
+ const st = servicesFeatureState(f, sstDec, ustDec, estDec, istDec);
+ if (st.sst === 'on' || st.ust === true || st.est === true || st.ist === true) out.push(f.f);
+ }
+ return out;
+}
+
+// FIDs present on the card: any node of the file-manager tree that the probe
+// confirmed (exists === true), DFs included - a service gating a whole DF
+// declares the DF's FID. A node that was never probed (exists === null) is
+// not counted, so an unprobed tree reports nothing as present, and a DF
+// confirmed absent is not descended into.
+function servicesFsPresentFids(root) {
+ const out = [];
+ const seen = {};
+ const walk = node => {
+ for (const ch of ((node && node.children) || [])) {
+ const fid = String(ch.fid || '').toUpperCase();
+ if (ch.exists === true && /^[0-9A-F]{4}$/.test(fid) && !seen[fid]) {
+ seen[fid] = true;
+ out.push(fid);
+ }
+ if (ch.isDir && ch.exists !== false) walk(ch);
+ }
+ };
+ walk(root);
+ return out;
+}
+
+// File names per FID from the same tree, for the report: a FID may carry one
+// name per DF (the collisions the note names), so the value is a list.
+function servicesFsFidNames(root) {
+ const names = {};
+ const walk = node => {
+ for (const ch of ((node && node.children) || [])) {
+ const fid = String(ch.fid || '').toUpperCase();
+ if (/^[0-9A-F]{4}$/.test(fid) && ch.name) {
+ if (!names[fid]) names[fid] = [];
+ if (names[fid].indexOf(ch.name) < 0) names[fid].push(ch.name);
+ }
+ if (ch.isDir && ch.exists !== false) walk(ch);
+ }
+ };
+ walk(root);
+ return names;
+}
+
+// The mismatch report, deterministic and DOM-free:
+// missing: [{ feature, fids }] - declared features with absent files, in
+// cross-reference order, `fids` in the map's (doc) order;
+// extra: [{ fid, features }] - present FIDs no declared feature explains,
+// sorted by FID, `features` = every feature mapping that FID (all
+// of them undeclared). A FID that any declared feature maps is
+// explained and not reported: several features legitimately share
+// one file (4F01 under the 5G/ACDC/MCPTT DFs, 6F4A/6F4B across
+// EXT1/EXT2 and BDN), and the FID-level rule cannot tell which DF
+// the present file sits in.
+function servicesFsReport(declared, presentFids) {
+ const declaredSet = {};
+ for (const name of (declared || [])) declaredSet[name] = true;
+ const presentSet = {};
+ for (const fid of (presentFids || [])) presentSet[String(fid).toUpperCase()] = true;
+ const missing = [];
+ const byFid = {};
+ for (const f of SERVICES_CROSS_REF) {
+ const fids = SERVICES_FS_MAP[f.f] || [];
+ for (const fid of fids) {
+ if (!byFid[fid]) byFid[fid] = [];
+ byFid[fid].push(f.f);
+ }
+ if (!declaredSet[f.f]) continue;
+ const absent = fids.filter(fid => !presentSet[fid]);
+ if (absent.length) missing.push({ feature: f.f, fids: absent });
+ }
+ const extra = Object.keys(byFid).sort()
+ .filter(fid => presentSet[fid] && !byFid[fid].some(name => declaredSet[name]))
+ .map(fid => ({ fid: fid, features: byFid[fid] }));
+ return { missing: missing, extra: extra };
+}
+
+function servicesFsReportHtml(report, names) {
+ const note = '
' +
+ esc(t('The check is FID-level: a FID is counted anywhere in the card\'s tree, so the collisions across DFs (6F4E EXT4/EXT5, 6F4F ECCP/CCP2, 6F07 IMSI/IST, 6F38 SST/UST) are not disambiguated.')) + '
';
+ const head = (text, n) => '
' + esc(text) + ' (' + n + ')
';
+ const item = text => '
' + text + '
';
+ // the tree's own name(s) for the FID, when the check loaded it
+ const fidLabel = fid => {
+ const list = names && names[fid];
+ return '' + esc(fid) + '' +
+ ((list && list.length) ? ' (' + esc(list.join(' / ')) + ')' : '');
+ };
+ const fidList = fids => fids.map(fidLabel).join(', ');
+ if (!report || (!report.missing.length && !report.extra.length)) {
+ return '
' + note;
+ }
+ let html = '';
+ if (report.missing.length) {
+ html += head(t('Declared services whose files are missing'), report.missing.length);
+ html += '
';
+ for (const m of report.missing) html += item(esc(m.feature) + ': ' + fidList(m.fids));
+ html += '
';
+ }
+ if (report.extra.length) {
+ html += head(t('Present files whose service is not declared'), report.extra.length);
+ html += '
';
+ for (const e of report.extra) html += item(fidLabel(e.fid) + ': ' + esc(e.features.join(', ')));
+ html += '
';
+ }
+ return html + note;
+}
+
// Presence + contents cached per card session, like the EF.ARR copies
// (pysimArrEnsure): the four files are read on open, again on Refresh and
// after a card-session change.
let _pysimServicesCache = null;
let _pysimServicesBusy = false;
+// The last filesystem-check report, kept so a re-render (language switch) can
+// redraw it; cleared with the cache on a card-session change. The names map
+// annotates the report's FIDs with the tree's file names.
+let _pysimServicesFsReport = null;
+let _pysimServicesFsNames = null;
+let _pysimServicesFsBusy = false;
async function pysimServicesReadFile(spec) {
try {
@@ -23454,10 +23785,59 @@ async function pysimServicesOpen(force) {
// A card-session change drops the cached reads and the rendered grid.
function pysimServicesReset() {
_pysimServicesCache = null;
+ _pysimServicesFsReport = null;
+ _pysimServicesFsNames = null;
pysimServicesRender();
if (isViewVisible('pysim-sub-services')) pysimServicesOpen();
}
+function pysimServicesFsRender() {
+ const el = document.getElementById('pysim-services-fs-report');
+ if (!el) return;
+ el.innerHTML = _pysimServicesFsReport ? servicesFsReportHtml(_pysimServicesFsReport, _pysimServicesFsNames) : '';
+}
+
+// "Check filesystem": compare the declared services with the card's probed
+// tree. The file manager loads its tree lazily and only probes presence on
+// demand, so the check runs the full probe once per tree (a loaded but
+// unprobed tree claims every listed file as present - /api/tree does not test
+// it).
+async function pysimServicesFsCheck() {
+ const statusEl = document.getElementById('pysim-services-fs-status');
+ const reportEl = document.getElementById('pysim-services-fs-report');
+ if (_pysimServicesFsBusy || !reportEl) return;
+ const setStatus = text => { if (statusEl) statusEl.textContent = text; };
+ if (_pysimFsProbe) { setStatus(t('A file probe is already running')); return; }
+ _pysimServicesFsBusy = true;
+ _pysimServicesFsReport = null;
+ _pysimServicesFsNames = null;
+ reportEl.innerHTML = '';
+ setStatus(t('Loading...'));
+ try {
+ const cache = await pysimServicesEnsure();
+ if (!cache || !cache.files || !pysimServicesColumns(cache.files).length) {
+ reportEl.innerHTML = '
' +
+ esc(t('No service table found on this card')) + '