Infer the card's ETSI release from its installed uicc.toolkit version

The explorer's card-resources block now names the card's ETSI release, using
the same mapping as the CAP analysis (capmem.ETSI_TOOLKIT_RELEASE: uicc.toolkit
version -> REL-N, basis in brackets; the live card's 1.12 -> REL-15/16/17).
The table is mirrored in the PWA (drift-guarded by tests/test_cap_memory.py,
like JC_FRAMEWORK_SDK); the version comes from the expanded registry's CE tag
and degrades to the version hint when the listing has none; SIM-only cards (no
uicc.toolkit) get no label, the same rule as SIM-only CAPs.

- cardEtsiRelease(elfs) (pure) + UICC_TOOLKIT_AID / ETSI_TOOLKIT_RELEASE in the
  PWA; rendered after the free-memory lines with a tooltip naming the basis.
- Tests: helper vectors (1.12/1.11/1.5, unknown version, versionless,
  SIM-only), the rendering guard, and the table/AID drift guard.
- Help EN/RU; sw cache simple-v354.
This commit is contained in:
2026-10-02 00:48:45 +03:00
parent 6e4eb5932c
commit 00181ba3ad
6 changed files with 91 additions and 4 deletions
+35 -1
View File
@@ -26,7 +26,7 @@ const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamB
'ramFormatIsExpanded', 'ramFmtLadder',
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'ramParseModuleAids', 'parseTLV', '_parseE3Entry', '_parseMenuEntries',
'ramExpandedReport', 'ramExpandedMore', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
'ramExpandedReport', 'ramExpandedMore', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'cardEtsiRelease', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramShowCntrLow',
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
'spKeysetOptionsHtml', 'spPresetIdx', 'tarPresetIdx',
@@ -50,6 +50,10 @@ const an = html.match(/const JC_AID_NAMES = \{[\s\S]*?\n\};/);
if (an) code += an[0].replace(/^const /, 'var ') + '\n';
const ar = html.match(/const JC_AID_RIDS = \{[\s\S]*?\n\};/);
if (ar) code += ar[0].replace(/^const /, 'var ') + '\n';
const et = html.match(/const ETSI_TOOLKIT_RELEASE = \{[\s\S]*?\n\};/);
if (et) code += et[0].replace(/^const /, 'var ') + '\n';
const ua = html.match(/const UICC_TOOLKIT_AID = '[^']*';/);
if (ua) code += ua[0].replace(/^const /, 'var ') + '\n';
globalThis.spPresetWarningRender = () => {};
globalThis.pysimApplyAvailability = () => {};
eval(code);
@@ -701,6 +705,36 @@ test('ramFmtLadder orders the registry command formats cautiously', () => {
assert.deepStrictEqual(ramFmtLadder('expanded-ae', 'expanded-ae'), ['expanded-ae', 'expanded', 'compact']);
});
test('cardEtsiRelease infers the card ETSI release from uicc.toolkit', () => {
const tk = v => ({ aid: 'A0000000090005FFFFFFFF8912000000', version: v });
assert.deepStrictEqual(cardEtsiRelease([tk('1.12')]),
{ release: 'REL-15/16/17', basis: 'uicc.toolkit 1.12' });
assert.deepStrictEqual(cardEtsiRelease([tk('1.11')]),
{ release: 'REL-12', basis: 'uicc.toolkit 1.11' });
assert.deepStrictEqual(cardEtsiRelease([tk('1.5')]),
{ release: 'REL-9', basis: 'uicc.toolkit 1.5' });
// a version outside the table: the basis is still named
assert.deepStrictEqual(cardEtsiRelease([tk('9.9')]),
{ release: '', basis: 'uicc.toolkit 9.9' });
// the compact listing carries no versions (the expanded CE tag does)
assert.deepStrictEqual(cardEtsiRelease([tk('')]), { release: '', basis: '' });
// no uicc.toolkit (SIM-only card): no label, like SIM-only CAPs
assert.strictEqual(cardEtsiRelease([{ aid: 'A0000000090003FFFFFFFF8910710002', version: '2.6' }]), null);
assert.strictEqual(cardEtsiRelease(null), null);
});
test('the explorer shows the inferred ETSI release', () => {
globalThis.t = s => s;
const out = ramRenderExploreHtml({ appCount: 1, freeNV: 2, freeV: 3 }, [], [],
[{ aid: 'A0000000090005FFFFFFFF8912000000', version: '1.12' }], 'compact', '', '');
delete globalThis.t;
assert.ok(out.includes('ETSI release: REL-15/16/17 (uicc.toolkit 1.12)'), out);
globalThis.t = s => s;
const none = ramRenderExploreHtml({ appCount: 1, freeNV: 2, freeV: 3 }, [], [], [], 'compact', '', '');
delete globalThis.t;
assert.ok(!none.includes('ETSI release:'), none);
});
test('ramExpandedReport names the expanded serving format', () => {
globalThis.t = s => s;
const out = ramExpandedReport([