feat: chained GET RESPONSE + ELF versions from app instances (v3.6.56)
The live log (2026-09-30) showed our standalone GET RESPONSE was wrong: the card answered 6700 (wrong length) to 00C00000<xx> for the 61xx answers of the expanded ISD query and the ELF notags query. The compact path proves how this card wants it: GET STATUS and GET RESPONSE chained in one compact command string (80F2..C0000000). - a 61xx answer now retries the same query with ramChainGetResponse (+ C0000000, compact format only; the expanded AA/22 form has no command string, so no retry there); - a 61xx page still continues to the next occurrence (the GET RESPONSE's own SW replaces the 6310 signal); the ISD stays a single occurrence; - the standalone 00C00000<xx> attempt is gone (it only ever answered 6700). This card also refuses every tag list for the ELF queries (tags/noea/lean all 6A88), so CE cannot come from the ELF listing. Its apps listing does carry the load file version (CE) together with the package AID (C4), so ramDeriveElfVersions fills a missing ELF version from its applet instance and the row annotates it: 'Version: 9.3 (from the app instance)'. The version hint now shows whenever any ELF row lacks a version. sw cache simple-v329; tests: 550 Python / 711 Node.
This commit is contained in:
+43
-10
@@ -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', 'ramGetResponseApdu',
|
||||
'ramExpandedReport', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions',
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
|
||||
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
let code = '';
|
||||
@@ -581,13 +581,45 @@ test('ramExpandedQueryApdu builds the expanded query forms', () => {
|
||||
assert.deepStrictEqual(RAM_EXPANDED_FORMS, ['tags', 'noea', 'lean', 'notags']);
|
||||
});
|
||||
|
||||
test('ramGetResponseApdu fetches a 61xx response (the announced length)', () => {
|
||||
assert.strictEqual(ramGetResponseApdu('6131'), '00C0000031');
|
||||
assert.strictEqual(ramGetResponseApdu('617F'), '00C000007F');
|
||||
assert.strictEqual(ramGetResponseApdu('613f'), '00C000003F');
|
||||
// not a 61xx: ask for all available data
|
||||
assert.strictEqual(ramGetResponseApdu('9000'), '00C0000000');
|
||||
assert.strictEqual(ramGetResponseApdu(''), '00C0000000');
|
||||
test('ramChainGetResponse chains the GET RESPONSE into the query string', () => {
|
||||
// the live card rejects a standalone GET RESPONSE with 6700; the compact
|
||||
// command string (query + C0000000) is how the compact path fetches too
|
||||
assert.strictEqual(ramChainGetResponse('80F280020C4F005C084F9F70C5C4CCCEEA00'),
|
||||
'80F280020C4F005C084F9F70C5C4CCCEEA00C0000000');
|
||||
assert.strictEqual(ramChainGetResponse('80F21002094F005C054F9F70CE8400'),
|
||||
'80F21002094F005C054F9F70CE8400C0000000');
|
||||
});
|
||||
|
||||
test('ramDeriveElfVersions reads a package version from its applet instance', () => {
|
||||
// the app entry carries CE (the load file version) and C4 (the package
|
||||
// AID), so an ELF whose own listing refused every tag list still gets one
|
||||
const elfs = [
|
||||
{ aid: 'A1130001180001FFFFFFFF89A1003900', lifecycle: '01', privileges: '00' },
|
||||
{ aid: 'F0414C46416001', lifecycle: '01', privileges: '00', version: '0100' },
|
||||
{ aid: 'DEADBEEF', lifecycle: '01', privileges: '00' },
|
||||
];
|
||||
const apps = [
|
||||
{ aid: 'A1130001180001FFFFFFFF89A1003908', version: '0903', elfAid: 'A1130001180001FFFFFFFF89A1003900' },
|
||||
{ aid: 'A0000005591010FFFFFFFF8900001200', version: '0001', elfAid: 'A0000005591010FFFFFFFF8900000E00' },
|
||||
];
|
||||
ramDeriveElfVersions(elfs, apps);
|
||||
assert.strictEqual(elfs[0].version, '0903');
|
||||
assert.strictEqual(elfs[0].versionFrom, 'app');
|
||||
// an own version is never overwritten, and an unmatched package stays bare
|
||||
assert.strictEqual(elfs[1].version, '0100');
|
||||
assert.strictEqual(elfs[1].versionFrom, undefined);
|
||||
assert.strictEqual(elfs[2].version, undefined);
|
||||
});
|
||||
|
||||
test('the explorer annotates a derived package version', () => {
|
||||
globalThis.t = s => s;
|
||||
const out = ramRenderExploreHtml(null, [], [],
|
||||
[{ aid: 'E1', lifecycle: '01', privileges: '00', version: '0903', versionFrom: 'app' }]);
|
||||
const plain = ramRenderExploreHtml(null, [], [],
|
||||
[{ aid: 'E1', lifecycle: '01', privileges: '00', version: '0903' }]);
|
||||
delete globalThis.t;
|
||||
assert.ok(out.includes('Version: 9.3 <span class="text-gray-500 dark:text-slate-400">(from the app instance)</span>'), out);
|
||||
assert.ok(!plain.includes('from the app instance'), plain);
|
||||
});
|
||||
|
||||
test('ramExpandedReport summarizes the query forms and the fallbacks', () => {
|
||||
@@ -671,8 +703,9 @@ test('the Explore detects the expanded query form and reports it', () => {
|
||||
// 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');
|
||||
// a 61xx answer holds the data (GET RESPONSE) and a 6310 pages
|
||||
assert.ok(src.includes('ramGetResponseApdu(sw)'), '61xx must be answered with GET RESPONSE');
|
||||
// a 61xx answer holds the data (chained GET RESPONSE) and a 6310 pages
|
||||
assert.ok(src.includes('ramChainGetResponse(apdu)'), '61xx must be answered with a chained GET RESPONSE');
|
||||
assert.ok(!src.includes('00C00000'), 'no standalone GET RESPONSE (it answers 6700)');
|
||||
assert.ok(src.includes('ramExpandedQueryApdu(p1, form, page > 1)'), '6310 must page');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user