diff --git a/docs/decoded-editing.md b/docs/decoded-editing.md index 63b43f5..5436722 100644 --- a/docs/decoded-editing.md +++ b/docs/decoded-editing.md @@ -303,16 +303,24 @@ de-allocates. - **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. 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. + feature name) maps a feature to the **symbolic files** its `Filesystem` cell + declares, resolved through the standard file list's `(name, root) -> path` + index — path-exact, so the same FID under another DF (EF.PBR/EF.SAI both + 4F30, EF.SST/EF.UST both 6F38) no longer explains a service. A **Check + filesystem** action 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 in any checked tree; + a declared feature the standard list cannot resolve is reported separately as + *unchecked*. Services that map to a capability rather than a file (CHV1 + disable, Proactive SIM, …) carry no file check. The declarations are read + per application (SST -> MF, UST/EST -> ADF.USIM, IST -> ADF.ISIM) and a + USIM/ISIM role may resolve into the common DF.TELECOM subtree (the MF tree), + except the GSM application's mapped copies (EF.ADN/EF.EXT1/EF.ECCP, TS 31.102 + 4.5). The follow-ups are the `ADF.USIM-NON-IMSI` root alias and the files + missing from the standard list (`DF.GRAPHICS`/`EF.IMG`, the DF.TELECOM + DF.SoLSA/DF.MExE, EF.3GPPPSDATAOFFservicelist) — `docs/services-editor.md` + §B.0. The last report is kept in memory (it carries its resolved paths) 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 118ca09..18d9691 100644 --- a/docs/services-editor.md +++ b/docs/services-editor.md @@ -10,8 +10,8 @@ > 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`. Step 4b -> (§B, the filesystem check) is implemented as well - see §B.0 for the -> representation and the FID-level rule it uses. +> (§B, the filesystem check) is implemented as well — see §B.0 for the +> representation and the path-exact `(name, root)` 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, @@ -231,44 +231,83 @@ The FS column in §A is deliverable B, merged for readability. Legend: `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: +list of **symbolic file names 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 +- `EF.FDN 6F3B (+ EF.EXT2 6F4B)` → `['EF.FDN', 'EF.EXT2']` +- `DF.GRAPHICS 5F50 → EF.IMG 4F20` → `['DF.GRAPHICS', 'EF.IMG']` (a DF's own + name counts: the DF is the file the service gates) +- `EF.NIA 6F51 (SIM) / 6FD3 (USIM)` → `['EF.NIA']` — one name, a different + path per tree - `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 names are the standard file list's (`frontend/uicc_files.json`, built from +pySim by `pysim_simple_server/uicc_files.py`) `name` values, so the check can +resolve them **path-exactly**: a FID alone is ambiguous across applications +(`4F30` is EF.PBR *and* EF.SAI, `6F38` EF.SST *and* EF.UST, `6F4E` EF.EXT4 +under DF.TELECOM *and* EF.EXT5 under ADF.USIM). `servicesPathIndexBuild()` +turns the list into a `NAME|ROOT → path` index (uppercased both sides; the one +duplicate — pySim models `EF.SUCI_Calc_Info` twice under ADF.USIM, in DF.5GS +`4F07` and in the eSIM profile package's DF.SAIP `4F01` — takes the list's +first entry, the DF.5GS one). `servicesPathIndex()` loads it once per page +through `uiccStdFilesLoad()`. + 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: +tree proves nothing), collects the tree's **present paths** (every node with +`exists === true`, DFs included, as canonical `pysimFsNodePath` paths) 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. +- **declared per tree** — `servicesFsDeclaredTrees()`: EF.SST declares in the + **MF** tree, EF.UST/EF.EST in **ADF.USIM**, EF.IST in **ADF.ISIM** (the state + the editor shows: EF.SST only at *allocated + activated*). A tree whose + service table is not on the card declares nothing there (its declarations + are unknown, so it is not checked at all). +- `missing` — a declared feature whose resolved paths are absent, in §A order + then MF / ADF.USIM / ADF.ISIM; each entry carries the declaring `tree` and + the absent `paths` (in the cell's order). +- `extra` — a present path whose mapped features are declared in **none** of + the checked trees, sorted by path, with the path's own `tree`, its `role` + name and every mapping feature. A path **any** declared feature maps is + explained: several features share one file (DF.GSM-ACCESS across "GSM + Access"/"GSM security context", EXT2 across FDN/Extension 2, the 5G DFs' + `4F01`/`4F02`) and a declaration in one application may gate a file of + another (a UST service gating a DF.TELECOM DF — see below). +- `unchecked` — a declared feature with **no** path at all: the standard list + is missing the file, so its presence cannot be verified. Today: + `Image (IMG)` (`DF.GRAPHICS`/`EF.IMG` — pySim does not model DF.GRAPHICS), + `SoLSA` and `MExE` in the MF tree (the DF.TELECOM copies of DF.SoLSA/DF.MExE + are not in the list) and `3GPP PS Data Off Service List` + (EF.3GPPPSDATAOFFservicelist). Reported instead of guessed — the follow-up + is to add those files to `SUPPLEMENT` in `pysim_simple_server/uicc_files.py`. -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. +**Resolution per declaring tree.** A role resolves in the declaring tree's +root first, then — for a USIM/ISIM declaration — in the **MF** tree, because +several UST-gated DFs are children of the common DF.TELECOM (TS 31.102 4.6.0: +DF.MULTIMEDIA `5F3B`, DF.MCS `5F3D`, DF.V2X `5F3E`, DF.A2X `5F3F`, +DF.GRAPHICS `5F50`), i.e. `MF/7F10/…` paths. The exceptions +(`SERVICES_FS_GSM_MAPPED`) are the files TS 31.102 4.5 lists as *mappings* of a +DF.PHONEBOOK file into DF.TELECOM "in case of a present GSM application" — +EF.ADN `6F3A`, EF.EXT1 `6F4A`, EF.ECCP `6F4F`: a USIM declaration's phonebook +is DF.PHONEBOOK + EF.PBR (ADR's `EF.ADN` is the SIM's file), so those are +never expected in a USIM/ISIM tree. A role the list knows for another root +only is skipped, not a failure — a feature's files are the union of its +applications' files (ADN = EF.ADN for the SIM, DF.PHONEBOOK + EF.PBR for the +USIM). -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. +**Not covered (follow-ups):** `ADF.USIM-NON-IMSI` — the list mirrors the +ADF.USIM files under that root, but a card's tree names the application +`ADF.USIM-non-IMSI`, so a non-IMSI USIM card's UST/EST paths are not matched +yet (resolve it as an alias of ADF.USIM); and the registry gaps listed above. + +The pure pieces (`servicesFsDeclaredTrees`, `servicesFsPresentPaths`, +`servicesPathIndexBuild`, `servicesFsRolePath`, `servicesFsReport`, +`servicesFsReportHtml`) are DOM/fetch-free and unit-tested; the last report is +kept in memory (it carries its resolved paths) 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) @@ -371,19 +410,31 @@ carries **147–150** (5MBS UE pre-configuration / Operator Controlled Signal Threshold per Access Technology / A2X / IMS Data Channel Indication), matching `UICC_FILES.md` §6.1 and TS 31.102 §4.2.8, so the tables above are complete. -### 5. Source-coverage gaps (cardless list only) +### 5. Source-coverage gaps (the standard file list) -The Services editor is **card-only**: it probes the equipped card by FID and -writes via `/api/write`, so it does **not** depend on `frontend/uicc_files.json` -(that is the *cardless* standard-file list feeding the SELECT picker and the -test-script file picker). The FS check probes the card directly, so the files -below missing from `uicc_files.json` are not a blocker — they matter only if a -cardless picker must offer them: EF.IMG 4F20 / DF.GRAPHICS 5F50, DF.MExE 5F3C + -EF.MExE-ST/ORPK/ARPK/TPRPK 4F40–4F43, DF.SoLSA 5F70 + EF.SAI 4F30/EF.SLL 4F31, -DF.BCAST 5F80, DF.ACDC 5FA0 + EF.ACDC_LIST 4F01, DF.TV 5FB0 + EF.TVUSD, -EF.TVCONFIG 6FFB, EF.3GPPPSDATAOFF 6FF9 / EF.3GPPPSDATAOFFservicelist 6FFA, -EF.IAL 6FF0 / EF.IPD 6FF2 (pairing), EF.PSISMSC 6FE5, and the 5G/A2X/5MBS -DFs (5FC0/5F3F/5F3E/5FF0/5FF1/5FE0). +The Services editor is **card-only**: it probes the equipped card's service +tables by path and writes via `/api/write`. The **FS check** additionally +needs the *cardless* standard file list (`frontend/uicc_files.json`) to resolve +a feature's symbolic files to paths (`(name, root) -> path`, §B.0) — the card's +own tree supplies presence, the list supplies the paths. Part 1 of step 4b +added the service-gated files pySim's model omits to `SUPPLEMENT` +(`pysim_simple_server/uicc_files.py`): EF.LRPLMNSI, EF.IAL, EF.IPD, +EF.TVCONFIG, EF.3GPPPSDATAOFF, DF.MExE + its files, DF.SoLSA + EF.SAI/EF.SLL, +DF.BCAST, DF.ACDC + EF.ACDC_LIST, DF.TV, DF.A2X + EF.AST/EF.A2X_CONFIG. + +Still missing from the list, and therefore reported as *unchecked* by the +check (a declared feature whose files have no path) rather than guessed: + +- **DF.GRAPHICS 5F50 → EF.IMG 4F20** — the DF.TELECOM child (TS 31.102 4.6.0); + pySim's SIM model does not carry DF.GRAPHICS at all. +- **DF.SoLSA 5F70 / DF.MExE 5F3C under DF.TELECOM** — the list carries them + under ADF.USIM only, so an *SST* declaration of SoLSA (service 40) or MExE + (49) cannot be checked in the MF tree. +- **EF.3GPPPSDATAOFFservicelist 6FFA** — a pySim TODO. + +A non-IMSI USIM (`ADF.USIM-NON-IMSI`) is mirrored in the list, but a card's +tree names the application `ADF.USIM-non-IMSI`; resolving the two as one root +is the follow-up (§B.0). ### 6. Deliberately unmapped diff --git a/frontend/index.html b/frontend/index.html index ff05eec..dc88fe8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -23301,192 +23301,208 @@ 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. +// Feature -> the *symbolic* files its Filesystem cell declares, transcribed +// from docs/services-editor.md §A (the merged "Filesystem" column, legend in +// §B): the file names only - `EF.FDN 6F3B (+ EF.EXT2 6F4B)` -> ['EF.FDN', +// 'EF.EXT2'], `DF.GRAPHICS 5F50 -> EF.IMG 4F20` -> ['DF.GRAPHICS', 'EF.IMG'] +// (a DF's own name counts: the DF is the file the service gates), `EF.NIA 6F51 +// (SIM) / 6FD3 (USIM)` -> ['EF.NIA'] (one name, a different path per tree). +// The names are the standard file list's (frontend/uicc_files.json) `name` +// values, so a role resolves to a path through (name, root) - the FID alone +// is ambiguous across applications (EF.PBR and EF.SAI are both 4F30, EF.SST +// and EF.UST are both 6F38). // `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. +// table* descriptors, so the name is taken. Coverage and the (name, root) +// resolution are pinned by frontend/tests/services.test.js. 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'], + 'Local Phone Book (ADN)': ['EF.ADN', 'DF.PHONEBOOK', 'EF.PBR'], + 'Fixed Dialling Numbers (FDN)': ['EF.FDN', 'EF.EXT2'], + 'Short Message Storage (SMS)': ['EF.SMS'], + 'Advice of Charge (AoC)': ['EF.ACM', 'EF.ACMmax', 'EF.PUCT'], + 'Capability Configuration Parameters (CCP)': ['EF.CCP'], + 'Capability Configuration Parameters 2 (CCP2)': ['EF.CCP2'], + 'PLMN selector': ['EF.PLMNsel'], + 'MSISDN': ['EF.MSISDN'], + 'Extension 1': ['EF.EXT1'], + 'Extension 2': ['EF.EXT2'], + 'SMS Parameters (SMSP)': ['EF.SMSP'], + 'Last Number Dialled (LND)': ['EF.LND'], + 'Cell Broadcast Message Identifier (CBMI)': ['EF.CBMI'], + 'Group Identifier Level 1 (GID1)': ['EF.GID1'], + 'Group Identifier Level 2 (GID2)': ['EF.GID2'], + 'Service Provider Name (SPN)': ['EF.SPN'], + 'Service Dialling Numbers (SDN)': ['EF.SDN', 'EF.EXT3'], + 'Extension 3': ['EF.EXT3'], + 'VGCS Group Identifier List': ['EF.VGCS', 'EF.VGCSS'], + 'VBS Group Identifier List': ['EF.VBS', 'EF.VBSS'], + 'enhanced Multi-Level Precedence and Pre-emption (eMLPP)': ['EF.eMLPP'], + 'Automatic Answer for eMLPP (AAeM)': ['EF.AAeM'], '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'], + 'Cell Broadcast Message Identifier Ranges (CBMIR)': ['EF.CBMIR'], + 'Barred Dialling Numbers (BDN)': ['EF.BDN', 'EF.EXT4'], + 'Extension 4': ['EF.EXT4'], + 'De-personalisation Control Keys (DCK)': ['EF.DCK'], + 'Co-operative Network List (CNL)': ['EF.CNL'], + 'Short Message Status Reports (SMSR)': ['EF.SMSR'], + 'Network\'s indication of alerting in the MS (NIA)': ['EF.NIA'], 'MO-SMS control (by SIM/USIM)': [], - 'GPRS': ['6F52', '7F20'], - 'Image (IMG)': ['5F50', '4F20'], - 'SoLSA': ['5F70', '4F30', '4F31'], + 'GPRS': ['EF.KcGPRS', 'DF.GSM'], + 'Image (IMG)': ['DF.GRAPHICS', 'EF.IMG'], + 'SoLSA': ['DF.SoLSA', 'EF.SAI', 'EF.SLL'], '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'], + 'User controlled PLMN selector with Access Technology': ['EF.PLMNwAcT'], + 'Operator controlled PLMN selector with Access Technology': ['EF.OPLMNwAcT'], + 'HPLMN selector with Access Technology': ['EF.HPLMNwAcT'], + 'CPBCCH Information': ['EF.CPBCCH'], + 'Investigation Scan': ['EF.InvScan'], + 'Extended Capability Configuration Parameters (ECCP)': ['EF.ECCP'], + 'MExE': ['DF.MExE', 'EF.MExE-ST'], + 'PLMN Network Name (PNN)': ['EF.PNN'], + 'Operator PLMN List (OPL)': ['EF.OPL'], + 'Mailbox Dialling Numbers (MBDN)': ['EF.MBDN', 'EF.EXT6'], + 'Message Waiting Indication Status (MWIS)': ['EF.MWIS', 'EF.MBI'], + 'Call Forwarding Indication Status (CFIS)': ['EF.CFIS', 'EF.EXT7'], + 'Service Provider Display Information (SPDI)': ['EF.SPDI'], + 'Multimedia Messaging Service (MMS)': ['EF.MMSN'], + 'Extension 8': ['EF.EXT8'], + 'MMS User Connectivity Parameters': ['EF.MMSICP', 'EF.MMSUCP', 'EF.MMSUP'], + 'Outgoing Call Information (OCI and OCT)': ['EF.OCI', 'EF.OCT'], + 'Incoming Call Information (ICI and ICT)': ['EF.ICI', 'EF.ICT'], + 'GSM Access': ['DF.GSM-ACCESS'], + 'Enabled Services Table': ['EF.EST'], + 'APN Control List (ACL)': ['EF.ACL'], + 'GSM security context': ['EF.Kc', 'EF.KcGPRS', 'DF.GSM-ACCESS'], + 'Extension 5': ['EF.EXT5'], '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'], + 'Pseudonym': ['EF.Pseudo', 'DF.WLAN'], + 'User Controlled PLMN selector for I-WLAN access': ['EF.UPLMNWLAN'], + 'Operator Controlled PLMN selector for I-WLAN access': ['EF.OPLMNWLAN'], + 'User controlled WSID list': ['EF.UWSIDL'], + 'Operator controlled WSID list': ['EF.OWSIDL'], + 'VGCS security': ['EF.VGCSCA'], + 'VBS security': ['EF.VBSCA'], + 'WLAN Reauthentication Identity': ['EF.WRI'], + 'Multimedia Messages Storage': ['EF.MML', 'EF.MMDF', 'DF.MULTIMEDIA'], + 'Generic Bootstrapping Architecture (GBA)': ['EF.GBABP', 'EF.GBANL', 'EF.NAFKCA'], + 'MBMS security': ['EF.MSK', 'EF.MUK'], 'Data download via USSD and USSD application mode': [], - 'Equivalent HPLMN': ['6FD9'], + 'Equivalent HPLMN': ['EF.EHPLMN'], 'Additional TERMINAL PROFILE after UICC activation': [], - 'Equivalent HPLMN Presentation Indication': ['6FDB'], - 'Last RPLMN Selection Indication': ['6FDC'], - 'OMA BCAST Smart Card Profile': ['5F80'], + 'Equivalent HPLMN Presentation Indication': ['EF.EHPLMNPI'], + 'Last RPLMN Selection Indication': ['EF.LRPLMNSI'], + 'OMA BCAST Smart Card Profile': ['DF.BCAST'], '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'], + 'Service Provider Name Icon': ['EF.SPNI'], + 'PLMN Network Name Icon': ['EF.PNNI'], + 'Connectivity Parameters for USIM IP connections': ['EF.NCP-IP'], + 'Home I-WLAN Specific Identifier List': ['EF.HWSIDL'], + 'I-WLAN Equivalent HPLMN Presentation Indication': ['EF.WEHPLMNPI'], + 'I-WLAN HPLMN Priority Indication': ['EF.WHPI'], + 'I-WLAN Last Registered PLMN': ['EF.WLRPLMN'], + 'EPS Mobility Management Information': ['EF.EPSLOCI', 'EF.EPSNSC'], + 'Allowed CSG Lists and corresponding indications': ['EF.ACSGL', 'EF.CSGT', 'EF.HNBN'], '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'], + 'HPLMN Direct Access': ['EF.HPLMNDAI'], + 'eCall Data': ['EF.SDN', 'EF.FDN'], + 'Operator CSG Lists and corresponding indications': ['EF.OCSGL', 'EF.OCSGT', 'EF.OHNBN'], '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'], + 'Non-Access Stratum configuration by USIM': ['EF.NASCONFIG'], + 'PWS configuration by USIM': ['EF.PWS'], 'URI support by UICC': [], 'Extended EARFCN support': [], - 'ProSe': ['5F90', '4F10'], - 'USAT Application Pairing': ['6FF1', '6FF0', '6FF2'], + 'ProSe': ['DF.ProSe', 'EF.PST'], + 'USAT Application Pairing': ['EF.IPS', 'EF.IAL', 'EF.IPD'], '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'], + 'ePDG configuration Information support': ['EF.ePDGSelection'], + 'ePDG configuration Information configured': ['EF.ePDGId'], + 'ACDC support': ['DF.ACDC', 'EF.ACDC_LIST'], + 'MCPTT': ['DF.MCS', 'EF.MST'], + 'ePDG configuration Information for Emergency Service support': ['EF.ePDGSelectionEm'], + 'ePDG configuration Information for Emergency Service configured': ['EF.ePDGIdEm'], + 'eCall Data over IMS': ['EF.SDNURI', 'EF.FDNURI'], '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'], + 'From Preferred': ['EF.FromPreferred'], + 'IMS configuration data': ['EF.IMSConfigData'], + 'TV configuration': ['EF.TVCONFIG', 'DF.TV'], + '3GPP PS Data Off': ['EF.3GPPPSDATAOFF'], + '3GPP PS Data Off Service List': ['EF.3GPPPSDATAOFFservicelist'], + 'V2X': ['DF.V2X', 'EF.VST', 'EF.V2X_CONFIG'], + 'XCAP Configuration Data': ['EF.XCAPConfigData'], + 'EARFCN list for MTC/NB-IOT UEs': ['EF.EARFCNList'], + '5GS Mobility Management Information': ['DF.5GS', 'EF.5GS3GPPLOCI', 'EF.5GSN3GPPLOCI'], + '5G Security Parameters': ['EF.5GAUTHKEYS', 'EF.5GS3GPPNSC', 'EF.5GSN3GPPNSC'], 'Subscription identifier privacy support': [], - 'SUCI calculation by the USIM': ['4F07'], - 'UAC Access Identities support': ['4F06'], + 'SUCI calculation by the USIM': ['EF.SUCI_Calc_Info'], + 'UAC Access Identities support': ['EF.UAC_AIC'], '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'], + '5GS Operator PLMN List': ['EF.OPL5G'], + 'Support for SUPI of type NSI or GLI or GCI': ['EF.SUPI_NAI'], '3GPP PS Data Off separate Home and Roaming lists': [], - 'Support for URSP by USIM': ['4F0B'], + 'Support for URSP by USIM': ['EF.URSP'], '5G Security Parameters extended': [], - 'MuD and MiD configuration data': ['6FFE'], + 'MuD and MiD configuration data': ['EF.MuDMiDConfigData'], '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'], + 'Support for multiple records of NAS security context storage for multiple registration': ['EF.5GS3GPPNSC', 'EF.5GSN3GPPNSC'], + 'Pre-configured CAG information list': ['EF.CAG'], + 'SOR-CMCI storage in USIM': ['EF.SOR-CMCI'], + '5G ProSe': ['DF.5G_ProSe', 'EF.5G_PROSE_ST'], + 'Storage of disaster roaming information in USIM': ['EF.DRI'], + 'Pre-configured eDRX parameters': ['EF.5GSEDRX'], + '5G NSWO support': ['EF.5GNSWO_CONF'], + 'PWS configuration for SNPN in USIM': ['EF.PWS_SNPN', 'DF.SNPN'], + 'Multiplier Coefficient for Higher Priority PLMN search via NG-RAN satellite access': ['EF.MCHPPLMN'], + 'K_AUSF derivation configuration': ['EF.KAUSF_DERIVATION'], + 'Network Identifier for SNPN (NID)': ['EF.NID', 'DF.SNPN'], + '5MBS UE pre-configuration': ['DF.5MBSUECONFIG', 'EF.5MBSUECONFIG'], + 'Operator Controlled Signal Threshold per Access Technology': ['EF.OCST'], + 'A2X': ['DF.A2X', 'EF.AST', 'EF.A2X_CONFIG'], + 'IMS Data Channel Indication': ['EF.IMSDCI'], + 'P-CSCF address': ['EF.P-CSCF'], 'HTTP Digest': [], 'Support of P-CSCF discovery for IMS Local Break Out': [], - 'WebRTC URI': ['6FFA'], - 'IMS Data Channel indication': ['6F0B'], + 'WebRTC URI': ['EF.WebRTCURI'], + 'IMS Data Channel indication': ['EF.IMSDCI'], }; +// The application whose service table declares a feature (the report's +// `tree`): EF.SST lists the SIM (MF) services, EF.UST/EF.EST the USIM's, +// EF.IST the ISIM's. +const SERVICES_FS_TREES = { sst: 'MF', ust: 'ADF.USIM', est: 'ADF.USIM', ist: 'ADF.ISIM' }; +// Report order of the trees: the SIM's first, then the applications. +const SERVICES_FS_TREE_ORDER = ['MF', 'ADF.USIM', 'ADF.ISIM']; +// A USIM/ISIM-declared file may live in the common DF.TELECOM subtree instead +// of the application's own DF (TS 31.102 4.6.0: DF.MULTIMEDIA '5F3B', +// DF.MCS '5F3D', DF.V2X '5F3E', DF.A2X '5F3F', DF.GRAPHICS '5F50' are +// children of DF.TELECOM, i.e. MF paths), so a role that has no path under +// the declaring application's root falls back to the MF tree. +// +// The exceptions are the files TS 31.102 4.5 lists as *mappings* of a +// DF.PHONEBOOK file into DF.TELECOM ("in case of a present GSM application"): +// EF.ADN '6F3A', EF.EXT1 '6F4A' and EF.ECCP '6F4F' under DF.TELECOM are the +// GSM application's backwards-compatible copies, so a USIM/ISIM declaration +// (whose phonebook lives in DF.PHONEBOOK, EF.PBR-referenced) must not expect +// them. A SIM (SST) declaration does, and resolves them in the MF tree. +const SERVICES_FS_GSM_MAPPED = ['EF.ADN', 'EF.EXT1', 'EF.ECCP']; + function servicesDecode(key, bytes) { if (key === 'sst') return efDecSst(bytes); return efDecServiceTable(bytes, SERVICES_TABLES[key]); @@ -23606,40 +23622,103 @@ function servicesFileBytes(key, values, files) { } // --- 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. +// The check is path-exact: a feature's symbolic files (SERVICES_FS_MAP) are +// resolved through the standard file list's (name, root) -> path index, and +// compared with the paths the card's probed tree confirms. The pure pieces +// are DOM/fetch-free: the index builder, the per-tree declaration sets, the +// present-path collector and the report. -// 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 = []; +// The (name, root) key of the path index. Both sides are uppercased so a +// card's tree names and the standard list's names compare directly. +function servicesFsPathKey(name, root) { + return String(name || '').toUpperCase() + '|' + String(root || '').toUpperCase(); +} + +// Canonical form of a path: uppercase, so the registry's and the tree's +// paths compare directly. +function servicesFsNormPath(path) { + return String(path || '').toUpperCase(); +} + +// NAME|ROOT -> canonical path, from the standard file list +// (frontend/uicc_files.json, loaded by uiccStdFilesLoad). A name is unique +// per root except for EF.SUCI_Calc_Info under ADF.USIM, which pySim models +// twice (DF.5GS/4F07, the UST service 125 file, and the eSIM profile +// package's DF.SAIP/4F01 copy) - the list's first entry wins, which is the +// DF.5GS one (the list is sorted, so this is deterministic). +function servicesPathIndexBuild(files) { + const index = new Map(); + for (const e of (files || [])) { + if (!e || !e.name || !e.root || !e.path) continue; + const key = servicesFsPathKey(e.name, e.root); + if (!index.has(key)) index.set(key, servicesFsNormPath(e.path)); + } + return index; +} + +let _servicesFsIndex = null; + +// The path index, loaded once per page from the standard file list (static +// and precached by the service worker, so the check works without a server +// round-trip for the list itself). +async function servicesPathIndex() { + if (!_servicesFsIndex) _servicesFsIndex = servicesPathIndexBuild(await uiccStdFilesLoad()); + return _servicesFsIndex; +} + +// The path a role resolves to in a declaring tree: the tree's own root first, +// then the common DF.TELECOM subtree (the MF tree) for a USIM/ISIM-declared +// role - except the GSM application's mapped copies, which only a SIM +// declaration expects (see SERVICES_FS_GSM_MAPPED). undefined when the +// standard list has no such file (the report's `unchecked`). +function servicesFsRolePath(index, role, root) { + if (!index) return undefined; + const own = index.get(servicesFsPathKey(role, root)); + if (own) return own; + if (root === 'MF') return undefined; + if (SERVICES_FS_GSM_MAPPED.indexOf(String(role || '').toUpperCase()) >= 0) return undefined; + return index.get(servicesFsPathKey(role, 'MF')); +} + +// Feature names the card declares, per declaring tree: { MF: [...], +// 'ADF.USIM': [...], 'ADF.ISIM': [...] } - the same decoded tables the grid +// renders, in cross-reference order. A tree is present only while its +// service table is on the card (without one the card declares nothing there, +// and every file of that area would look unused); EF.SST counts a feature +// only at 'on' (allocated + activated), as the editor renders it. +function servicesFsDeclaredTrees(files) { + const dec = key => (files && files[key] && files[key].present) ? (files[key].dec || null) : null; + const decs = { sst: dec('sst'), ust: dec('ust'), est: dec('est'), ist: 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); + const st = servicesFeatureState(f, decs.sst, decs.ust, decs.est, decs.ist); + for (const spec of SERVICES_FILES) { + if (!decs[spec.key]) continue; + if (spec.key === 'sst' ? st.sst !== 'on' : st[spec.key] !== true) continue; + const root = SERVICES_FS_TREES[spec.key]; + const list = out[root] || (out[root] = []); + if (list.indexOf(f.f) < 0) list.push(f.f); + } } return out; } -// FIDs present on the card: any node of the file-manager tree that the probe +// Paths present on the card: every node of the file-manager tree 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) { +// declares the DF's path. A node that was never probed (exists === null) is +// not counted, and an absent DF is not descended into (the probe does not +// fetch its children either). +function servicesFsPresentPaths(root) { const out = []; - const seen = {}; + const seen = new Set(); 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.exists === true) { + const p = servicesFsNormPath(pysimFsNodePath(ch)); + if (p && !seen.has(p)) { + seen.add(p); + out.push(p); + } } if (ch.isDir && ch.exists !== false) walk(ch); } @@ -23648,86 +23727,111 @@ function servicesFsPresentFids(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 }); +// missing: [{ feature, tree, paths }] - a declared feature whose resolved +// paths are absent, in cross-reference order then +// SERVICES_FS_TREE_ORDER, `paths` in the map's (doc) role order; +// `tree` is the application whose service table declares it; +// extra: [{ path, role, tree, features }] - a present path whose mapped +// features are declared in none of the checked trees, sorted by +// path; `tree` is the path's own root and `features` every +// feature mapping that path (all of them undeclared). A path +// any declared feature maps is explained and not reported: several +// features share one file (the 5G DFs' 4F01/4F02, EXT2 across +// FDN/Bdn, the DF.TELECOM DFs a USIM service gates); +// unchecked: [{ feature, tree, roles }] - a declared feature whose roles +// resolve to no path in that tree at all (the standard list is +// missing the file: DF.GRAPHICS/EF.IMG, the DF.TELECOM copies of +// DF.SoLSA/DF.MExE, EF.3GPPPSDATAOFFservicelist), so its presence +// cannot be verified - reported instead of guessed. +// A role the list has for another root only is skipped, not a failure: a +// feature's files are the union of its applications' files (ADN's EF.ADN is +// the SIM's, its USIM copy is DF.PHONEBOOK + EF.PBR). +function servicesFsReport(declaredByTree, index, presentPaths) { + const declared = declaredByTree || {}; + const present = new Set((presentPaths || []).map(servicesFsNormPath)); + const trees = SERVICES_FS_TREE_ORDER.filter(root => Array.isArray(declared[root])); + const declaredIn = {}; + const allDeclared = new Set(); + for (const root of trees) { + declaredIn[root] = new Set(declared[root]); + for (const name of declared[root]) allDeclared.add(name); } - 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 }; + const missing = []; + const unchecked = []; + const reverse = new Map(); + for (const f of SERVICES_CROSS_REF) { + const roles = SERVICES_FS_MAP[f.f] || []; + if (!roles.length) continue; + for (const root of trees) { + const paths = []; + for (const role of roles) { + const path = servicesFsRolePath(index, role, root); + if (!path) continue; + if (paths.indexOf(path) < 0) paths.push(path); + let rev = reverse.get(path); + if (!rev) { + rev = { path: path, role: role, tree: path.split('/')[0], features: [] }; + reverse.set(path, rev); + } + if (rev.features.indexOf(f.f) < 0) rev.features.push(f.f); + } + if (!declaredIn[root].has(f.f)) continue; + if (!paths.length) { + unchecked.push({ feature: f.f, tree: root, roles: roles.slice() }); + continue; + } + const absent = paths.filter(path => !present.has(path)); + if (absent.length) missing.push({ feature: f.f, tree: root, paths: absent }); + } + } + const extra = Array.from(reverse.values()) + .filter(e => present.has(e.path) && !e.features.some(name => allDeclared.has(name))) + .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) + .map(e => ({ path: e.path, role: e.role, tree: e.tree, features: e.features.slice() })); + return { missing: missing, extra: extra, unchecked: unchecked }; } -function servicesFsReportHtml(report, names) { +function servicesFsReportHtml(report) { const note = '