feat: expanded registry on by default, form ladder + fallback report (v3.6.54)

The 'Fetch expanded registry details' checkbox is now on by default: the
expanded pass carries the package versions, the SD/load-file associations
and the applet menu entries; an explicit stored opt-out is still honoured.

Live 2026-09-30: a card with a non-default ISD AID answered 6A88 to the
reference expanded query (P2=02 + the tag list), so the ELF rows had no
versions.  Cards differ in what they accept, so the query is now tried in
three forms (RAM_EXPANDED_FORMS):

  tags   - the reference tag list (incl. the TS 102 226 EA SCP Registry Data)
  noea   - the same list without EA (a card that errors on it)
  notags - no tag list at all (GPPro's 4F00-only data field); the card
           builds the default E3

The first form that answers is remembered for the run and tried first for
the later listings; the ISD gets a single attempt - its 6A88/6A86 is normal
('no data to report', GPPro) and must not poison the detection for the
apps/ELF queries.

Every listing's outcome is reported in the explorer (ramExpandedReport):
'Expanded registry: tag list + EA (Apps, ELFs) - ISD: no expanded data
(6A88) - compact listing used', or 'Expanded registry refused by the card -
compact listing only (package versions unavailable)'; the ELF section hints
when no version came back.  Tooltip/help (EN/RU) updated.

sw cache simple-v327; tests: 550 Python / 707 Node.
This commit is contained in:
2026-09-30 09:44:39 +03:00
parent 4906c91463
commit aa4bf8e008
7 changed files with 217 additions and 44 deletions
+70 -7
View File
@@ -25,6 +25,7 @@ function extractFunc(src, name) {
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtVersion', 'ramMenuState', 'ramEntryKind', 'ramMenuHtml', 'ramRawTlvsHtml', 'ramNvLine', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramMergeExpanded', 'ramStepLine', 'ramStepComponents', 'ramInstallFailHint', 'ramProbeParse', 'ramCompatVerdict', 'ramCompatProbeNote', 'ramCompatImportRows', 'ramCompatRowsHtml', 'ramCompatFailureHtml', 'ramCompatDetailHtml', 'lookupSw', 'ramExpandedDetailsInit', 'ramExpandedDetailsChanged', 'ramGetStatusApdu', 'ramDeleteApdu',
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'ramParseModuleAids', 'parseTLV', '_parseE3Entry', '_parseMenuEntries',
'ramExpandedReport',
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
let code = '';
@@ -35,6 +36,8 @@ const m = html.match(/const _chains = \{\};/);
if (m) code += m[0].replace(/^const /, 'var ') + '\n';
const swm = html.match(/const SW_MAP = \{[\s\S]*?\n\};/);
if (swm) code += swm[0].replace(/^const /, 'var ') + '\n';
const ef = html.match(/const RAM_EXPANDED_FORMS = \[[^\]]*\];/);
if (ef) code += ef[0].replace(/^const /, 'var ') + '\n';
const pn = html.match(/const PRIVILEGE_NAMES = \[[\s\S]*?\n\];/);
if (pn) code += pn[0].replace(/^const /, 'var ') + '\n';
const an = html.match(/const JC_AID_NAMES = \{[\s\S]*?\n\};/);
@@ -546,19 +549,76 @@ test('ramCardIdxAfterRemove keeps the remembered index aligned', () => {
assert.strictEqual(ramCardIdxAfterRemove(null, 1), null);
});
test('ramExpandedQueryApdu matches the reference trace bytes', () => {
// TCA Loader: `AA14 2212 80F28002 0C 4F00 5C08 4F9F70C5C4CCCEEA 00`
test('ramExpandedQueryApdu builds the three expanded query forms', () => {
// the reference trace form (richest: the tag list incl. the TS 102 226 EA)
for (const p1 of ['80', '40']) {
assert.strictEqual(ramExpandedQueryApdu(p1),
'80F2' + p1 + '020C4F005C084F9F70C5C4CCCEEA00');
assert.strictEqual(ramExpandedQueryApdu(p1, 'tags'), ramExpandedQueryApdu(p1));
}
// The ELF queries add '84' (Executable Module AIDs): with a tag list the
// card returns only the listed tags, so the expanded fallback would
// otherwise list packages without their modules (v3.6.51).
// the ELF queries add '84' (Executable Module AIDs)
for (const p1 of ['20', '10']) {
assert.strictEqual(ramExpandedQueryApdu(p1),
'80F2' + p1 + '020D4F005C094F9F70C5C4CCCEEA8400');
}
// without EA: a card that errors on the requested-but-absent EA object
assert.strictEqual(ramExpandedQueryApdu('40', 'noea'), '80F240020B4F005C074F9F70C5C4CCCE00');
assert.strictEqual(ramExpandedQueryApdu('10', 'noea'), '80F210020C4F005C084F9F70C5C4CCCE8400');
// no tag list at all (GPPro's GET STATUS data field: 4F00 only)
assert.strictEqual(ramExpandedQueryApdu('80', 'notags'), '80F28002024F0000');
assert.strictEqual(ramExpandedQueryApdu('10', 'notags'), '80F21002024F0000');
assert.deepStrictEqual(RAM_EXPANDED_FORMS, ['tags', 'noea', 'notags']);
});
test('ramExpandedReport summarizes the query forms and the fallbacks', () => {
globalThis.t = s => s;
// every listing answered the reference form
assert.strictEqual(ramExpandedReport([
{ p1: '80', label: 'ISD', compact: true, form: 'tags', sw: '9000' },
{ p1: '40', label: 'Apps', compact: true, form: 'tags', sw: '9000' },
{ p1: '10', label: 'ELFs', compact: true, form: 'tags', sw: '9000' },
]), 'Expanded registry: tag list + EA (ISD, Apps, ELFs)');
// the ISD commonly has no expanded data (6A88/6A86 is normal there)
assert.strictEqual(ramExpandedReport([
{ p1: '80', label: 'ISD', compact: true, form: null, sw: '6A88' },
{ p1: '40', label: 'Apps', compact: true, form: 'tags', sw: '9000' },
{ p1: '10', label: 'ELFs', compact: true, form: 'noea', sw: '9000' },
]), 'Expanded registry: tag list + EA (Apps) \u00b7 tag list (ELFs) \u00b7 ISD: no expanded data (6A88) \u2014 compact listing used');
// a listing the card refused entirely: the compact listing carried it
assert.strictEqual(ramExpandedReport([
{ p1: '40', label: 'Apps', compact: true, form: null, sw: '6A88' },
{ p1: '10', label: 'ELFs', compact: true, form: 'notags', sw: '9000' },
]), 'Expanded registry: no tag list (ELFs) \u00b7 Apps: refused (6A88) \u2014 compact listing used');
// nothing answered: the headline says the versions are unavailable
assert.strictEqual(ramExpandedReport([
{ p1: '80', label: 'ISD', compact: true, form: null, sw: '6A88' },
{ p1: '10', label: 'ELFs', compact: true, form: null, sw: '6A86' },
]), 'Expanded registry refused by the card \u2014 compact listing only (package versions unavailable) ISD: no expanded data (6A88) \u2014 compact listing used \u00b7 ELFs: refused (6A86) \u2014 compact listing used');
// a listing with no compact data either reports its own status word
assert.strictEqual(ramExpandedReport([
{ p1: '10', label: 'ELFs', compact: false, form: null, sw: '6A88' },
]), 'Expanded registry refused by the card \u2014 compact listing only (package versions unavailable) ELFs: 6A88');
assert.strictEqual(ramExpandedReport([]), '');
delete globalThis.t;
});
test('the explorer hints when the card returned no package versions', () => {
globalThis.t = s => s;
const noVer = ramRenderExploreHtml(null, [], [], [{ aid: 'E1', lifecycle: '01', privileges: '00' }]);
const withVer = ramRenderExploreHtml(null, [], [], [{ aid: 'E1', lifecycle: '01', privileges: '00', version: '0100' }]);
delete globalThis.t;
assert.ok(noVer.includes('Package versions come from the expanded registry query'), noVer);
assert.ok(!withVer.includes('Package versions come from the expanded registry query'), withVer);
});
test('the Explore detects the expanded query form and reports it', () => {
const src = extractFunc(html, 'ramExplore');
assert.ok(src.includes('RAM_EXPANDED_FORMS'), 'the form ladder must be used');
assert.ok(src.includes('expandedReport.push'), 'each listing outcome must be recorded');
assert.ok(src.includes('ramExpandedReport(expandedReport)'), 'the report must reach the explorer');
// the ISD gets a single attempt: its 6A88 must not poison the detection
assert.ok(src.includes("if (p1 === '80') forms = [expandedForm || 'tags'];"),
'the ISD must not walk the ladder');
});
test('ramParseAppStatus parses the expanded E3 listing (vendor traces)', () => {
@@ -1199,13 +1259,16 @@ test('the expanded-details checkbox is a sticky preference', () => {
};
els['ram-expanded-details'].checked = false;
ramExpandedDetailsInit();
assert.strictEqual(els['ram-expanded-details'].checked, false); // default: off
assert.strictEqual(els['ram-expanded-details'].checked, true); // default: on (versions live there)
store['simple_ram_expanded'] = '0';
ramExpandedDetailsInit();
assert.strictEqual(els['ram-expanded-details'].checked, false); // an explicit opt-out is honoured
store['simple_ram_expanded'] = '1';
ramExpandedDetailsInit();
assert.strictEqual(els['ram-expanded-details'].checked, true); // stored choice applied
els['ram-expanded-details'].checked = false;
ramExpandedDetailsChanged();
assert.strictEqual(store['simple_ram_expanded'], '0'); // ticking stores it
assert.strictEqual(store['simple_ram_expanded'], '0'); // unticking stores it
delete globalThis.localStorage;
});