feat: Explore expanded (E3) listing fallback + details pass + format display (v3.6.25)

The E3 parsers existed since the vendor-trace work; what was missing was
issuing the query.  Per GP 11.4.2.2 the compact listing format (P2.b2=0) is
deprecated and a card may reject it, while the expanded form (P2=02 + the
tag list 4F 9F70 C5 C4 CC CE EA) returns the richer registry data.

- ramExpandedQueryApdu(p1): the reference trace bytes
  (80F2 <p1> 02 0C 4F005C084F9F70C5C4CCCEEA 00), pinned in a test.
- Explore: when the compact pass for a P1 yields nothing, the query is
  retried in the expanded format and the result becomes the listing (the
  compact error only surfaces if the retry also fails); the new "Query
  expanded registry details (E3)" checkbox runs the expanded pass on top of
  a working compact listing and merges the expanded-only fields
  (privileges, ELF AID C4, SD AID CC, module lists) into the entries.
- The detected RAM command format (v3.6.24 ram_format) is now shown in the
  Explore result + explorer view, the delete verdict and the plain-send
  message.
- Tests parse the real trace payloads (NC ISD, ET application, NP ELF E3
  templates); help EN/RU updated.

656 frontend / 504 Python green; version 3.6.25; sw simple-v298.
This commit is contained in:
2026-09-28 22:51:20 +03:00
parent cf931da2f9
commit 7c81f02308
7 changed files with 101 additions and 16 deletions
+36 -1
View File
@@ -23,7 +23,7 @@ function extractFunc(src, name) {
// Extract chain builder functions and dependencies
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramGetStatusApdu', 'ramDeleteApdu',
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance',
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'parseTLV', '_parseE3Entry',
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute',
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
@@ -382,6 +382,41 @@ 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`
for (const p1 of ['80', '40', '20', '10']) {
assert.strictEqual(ramExpandedQueryApdu(p1),
'80F2' + p1 + '020C4F005C084F9F70C5C4CCCEEA00');
}
});
test('ramParseAppStatus parses the expanded E3 listing (vendor traces)', () => {
// Explore_NC.log, ISD: E3 { 4F AID, 9F70 LC, C5 privileges }
const isd = ramParseAppStatus('E3134F08A0000000030000009F70010FC5039AFE80');
assert.strictEqual(isd.length, 1);
assert.strictEqual(isd[0].aid, 'A000000003000000');
assert.strictEqual(isd[0].lifecycle, '0F');
assert.strictEqual(isd[0].privileges, '9AFE80');
assert.strictEqual(isd[0].type, 'app');
// Explore_ET.log, application entry: adds the ELF AID (C4) and SD AID (CC)
const apps = ramParseAppStatus('E3374F10A1130001180001FFFFFFFF89A10039089F700107C503000000C410A1130001180001FFFFFFFF89A1003900CC08A000000151000000');
assert.strictEqual(apps.length, 1);
assert.strictEqual(apps[0].aid, 'A1130001180001FFFFFFFF89A1003908');
assert.strictEqual(apps[0].elfAid, 'A1130001180001FFFFFFFF89A1003900');
assert.strictEqual(apps[0].sdAid, 'A000000151000000');
assert.strictEqual(apps[0].privileges, '000000');
});
test('ramParseElfStatus parses the expanded E3 listing (NP trace)', () => {
const elfs = ramParseElfStatus('E31B4F07A00000015153509F700101CE020100CC08A000000151000000');
assert.strictEqual(elfs.length, 1);
assert.strictEqual(elfs[0].aid, 'A0000001515350');
assert.strictEqual(elfs[0].lifecycle, '01');
assert.strictEqual(elfs[0].version, '0100');
assert.strictEqual(elfs[0].sdAid, 'A000000151000000');
assert.strictEqual(elfs[0].type, 'elf');
});
test('ramParseElfStatus lists the compact ELF and module listings (F0414C46416101)', () => {
// Exact bytes from a live RAM Explore: the P1=20 ELF page and the P1=10
// (ELF+modules) page. The old 0x10-scan walk dropped everything after the