fix: per-AID version queries - module->applet mapping + chained GET RESPONSE (v3.7.1)
Two bugs in the v3.7.0 per-AID fallback, both visible in the live log (2026-09-30 11:21): - the candidate applet was searched by the AID prefix, but the applet instance AIDs are the package's module AIDs (package A113...0200 has modules ...0208 / ...494D4508, both present as applet instances - they differ from the package AID in the last byte), so that package produced no query at all. ramElfAppletCandidate (pure) prefers the module AIDs and keeps the shared-prefix rule as a fallback; - the single-AID expanded query answered 6134 (the card holds the data) and the empty response was read, so no version was derived. The query is now retried with the GET RESPONSE chained into the compact command string (ramChainGetResponse) exactly like the ladder - a standalone GET RESPONSE answers 6700 on this card. Tests: the module->applet mapping with the live AIDs (incl. the versioned applet being skipped and the prefix fallback), plus the source guards for the chained retry. sw cache simple-v333; tests: 557 Python / 717 Node.
This commit is contained in:
@@ -25,7 +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', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions',
|
||||
'ramExpandedReport', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
|
||||
'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramShowCntrLow',
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
|
||||
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
@@ -618,6 +618,31 @@ test('ramDeriveElfVersions reads a package version from its applet instance', ()
|
||||
assert.strictEqual(elfs[2].version, undefined);
|
||||
});
|
||||
|
||||
test('ramElfAppletCandidate maps a package to its applet instance', () => {
|
||||
// live 2026-09-30: the applet instance AIDs are the package's module AIDs
|
||||
// (they differ from the package AID in the last byte)
|
||||
const apps = [
|
||||
{ aid: 'A1130001180002FFF7100E8904000208' },
|
||||
{ aid: 'A1130001180002FFF7100E89494D4508' },
|
||||
{ aid: 'F0414C4641600101' },
|
||||
{ aid: 'A1130001180001FFFFFFFF89A1003908', version: '0903' },
|
||||
];
|
||||
assert.strictEqual(ramElfAppletCandidate({ aid: 'A1130001180002FFF7100E8904000200',
|
||||
moduleAids: ['A1130001180002FFF7100E8904000208', 'A1130001180002FFF7100E89494D4508'] }, apps).aid,
|
||||
'A1130001180002FFF7100E8904000208');
|
||||
assert.strictEqual(ramElfAppletCandidate({ aid: 'F0414C46416001',
|
||||
moduleAids: ['F0414C4641600101'] }, apps).aid, 'F0414C4641600101');
|
||||
// an applet that already carries a version is skipped, and a package whose
|
||||
// module is that applet has no other candidate
|
||||
assert.strictEqual(ramElfAppletCandidate({ aid: 'A1130001180001FFFFFFFF89A1003900',
|
||||
moduleAids: ['A1130001180001FFFFFFFF89A1003908'] }, apps), null);
|
||||
// a package with no applet at all
|
||||
assert.strictEqual(ramElfAppletCandidate({ aid: 'A00000006203010101', moduleAids: [] }, apps), null);
|
||||
assert.strictEqual(ramElfAppletCandidate(null, apps), null);
|
||||
// the shared-prefix rule is the fallback
|
||||
assert.strictEqual(ramElfAppletCandidate({ aid: 'ABCD' }, [{ aid: 'ABCDEF' }]).aid, 'ABCDEF');
|
||||
});
|
||||
|
||||
test('the explorer annotates a derived package version', () => {
|
||||
globalThis.t = s => s;
|
||||
const out = ramRenderExploreHtml(null, [], [],
|
||||
@@ -762,6 +787,10 @@ test('the Explore retries the ELF tag lists with P1=20 and per applet AID', () =
|
||||
assert.ok(src.includes('elfPerAidVersions'), 'the per-AID version queries must exist');
|
||||
assert.ok(src.includes("ramExpandedQueryApdu('40', 'tags', false, app.aid)"),
|
||||
'the per-AID query must use the AID filter');
|
||||
assert.ok(src.includes('ramElfAppletCandidate(e, apps)'),
|
||||
'the module->applet mapping must be used');
|
||||
assert.ok(src.includes('ramChainGetResponse(apdu)'),
|
||||
'the single-AID 61xx answer must be fetched with a chained GET RESPONSE');
|
||||
});
|
||||
|
||||
test('every SCP80/RAM flow checks the low counter and stops', () => {
|
||||
|
||||
Reference in New Issue
Block a user