feat: name standard package AIDs in the Explore / SCP81 / R-APDU views (v3.5.7)
The RAM Explore view, the SCP81 GET STATUS script results and the R-APDU parser tree showed package AIDs as bare hex. A shared resolver now annotates the known standard JavaCard / ETSI / 3GPP / GlobalPlatform package AIDs with their library name (workspace export-file study `docs/JAVACARD.md`, 2026-09-26, plus the GP default ISD AID from GP Card Spec v2.3.1 H.1.3); a RID table gives a weak owner hint for otherwise unknown AIDs, and vendor/applet AIDs stay bare. - JC_AID_NAMES / JC_AID_RIDS + jcAidName / jcAidSuffix / jcAidHtml. - Wired into ramRenderExploreHtml (ISD, applications, ELFs, module and SD AIDs), scp81ResultLines GET STATUS listings and decodeTlvValue (4F/84/C4/CC - the R-APDU parser tree). - aid_names.test.js (families, normalisation, RID hints, malformed input, table sanity) plus render assertions in ram.test.js and scp81.test.js. - Help EN/RU note the annotation; table is hand-maintained from the note. 561 frontend / 421 Python green; version 3.5.7; sw cache simple-v260.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
||||
|
||||
function extractBlock(startMarker, endMarker) {
|
||||
const start = html.indexOf(startMarker);
|
||||
const end = html.indexOf(endMarker, start);
|
||||
if (start < 0 || end < 0) throw new Error('block not found: ' + startMarker);
|
||||
return html.slice(start, end);
|
||||
}
|
||||
|
||||
function extractFunc(src, name) {
|
||||
const re = new RegExp('function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
|
||||
const m = re.exec(src);
|
||||
if (!m) throw new Error('function ' + name + ' not found');
|
||||
let i = m.index + m[0].length - 1;
|
||||
let depth = 0;
|
||||
for (; i < src.length; i++) {
|
||||
if (src[i] === '{') depth++;
|
||||
else if (src[i] === '}') {
|
||||
depth--;
|
||||
if (depth === 0) break;
|
||||
}
|
||||
}
|
||||
return src.slice(m.index, i + 1);
|
||||
}
|
||||
|
||||
// Rewrite top-level const -> var so the tables leak out of sloppy-mode eval.
|
||||
eval(extractBlock('const JC_AID_NAMES = {', 'const JC_AID_RIDS = {').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const JC_AID_RIDS = {', 'function jcAidName').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'jcAidName'));
|
||||
eval(extractFunc(html, 'jcAidSuffix'));
|
||||
eval(extractFunc(html, 'jcAidHtml'));
|
||||
globalThis.esc = s => s;
|
||||
|
||||
test('the table resolves each standard library family', () => {
|
||||
// Oracle / JavaCard, ETSI (SIM and UICC era), 3GPP, GlobalPlatform
|
||||
assert.strictEqual(jcAidName('A0000000620101'), 'javacard.framework');
|
||||
assert.strictEqual(jcAidName('A000000062010101'), 'javacard.framework.service');
|
||||
assert.strictEqual(jcAidName('A0000000090003FFFFFFFF8910710002'), 'sim.toolkit');
|
||||
assert.strictEqual(jcAidName('A0000000090005FFFFFFFF8912000000'), 'uicc.toolkit');
|
||||
assert.strictEqual(jcAidName('A0000000871005FFFFFFFF8913200000'), 'uicc.usim.toolkit');
|
||||
assert.strictEqual(jcAidName('A00000015100'), 'org.globalplatform');
|
||||
// JAVACARD.md section 5: recognisable, not linkable libraries
|
||||
assert.strictEqual(jcAidName('A00000006203010F'), 'com.sun.javacard.crypto');
|
||||
assert.strictEqual(jcAidName('A0000000030000'), 'visa/openplatform');
|
||||
assert.strictEqual(jcAidName('123456654011'), 'com.denis');
|
||||
});
|
||||
|
||||
test('the default ISD resolves and unknown AIDs get a RID hint only', () => {
|
||||
assert.strictEqual(jcAidName('A000000151000000'), 'GlobalPlatform Issuer Security Domain');
|
||||
assert.strictEqual(jcAidName('A0000001515350'), 'GlobalPlatform RID');
|
||||
assert.strictEqual(jcAidName('A0000000871002FFFFFFFF8907090000'), '3GPP RID');
|
||||
// no pinned name for the legacy ISD AID, and vendor AIDs stay unnamed
|
||||
assert.strictEqual(jcAidName('A000000003000000'), '');
|
||||
assert.strictEqual(jcAidName('D276000005AAFFCAFE0010'), '');
|
||||
assert.strictEqual(jcAidName('A1130001180001'), '');
|
||||
});
|
||||
|
||||
test('the resolver normalizes case and spacing and rejects malformed input', () => {
|
||||
assert.strictEqual(jcAidName('a0 00 00 00 62 01 01'), 'javacard.framework');
|
||||
assert.strictEqual(jcAidName('a0000000620101'), 'javacard.framework');
|
||||
assert.strictEqual(jcAidName(''), '');
|
||||
assert.strictEqual(jcAidName(null), '');
|
||||
assert.strictEqual(jcAidName(undefined), '');
|
||||
assert.strictEqual(jcAidName('AB'), '');
|
||||
assert.strictEqual(jcAidName('A000000062010'), ''); // odd hex length
|
||||
});
|
||||
|
||||
test('the plain-text and HTML suffixes name known AIDs only', () => {
|
||||
assert.strictEqual(jcAidSuffix('A0000000620101'), ' (javacard.framework)');
|
||||
assert.strictEqual(jcAidSuffix('A0000001515350'), ' (GlobalPlatform RID)');
|
||||
assert.strictEqual(jcAidSuffix('D276000005AAFFCAFE0010'), '');
|
||||
globalThis.esc = s => 'E(' + s + ')';
|
||||
assert.strictEqual(jcAidHtml('A0000000620101'),
|
||||
'E(A0000000620101) <span class="text-gray-400 dark:text-slate-500">(E(javacard.framework))</span>');
|
||||
assert.strictEqual(jcAidHtml('D276000005AAFFCAFE0010'), 'E(D276000005AAFFCAFE0010)');
|
||||
globalThis.esc = s => s;
|
||||
});
|
||||
|
||||
test('the package table is well-formed', () => {
|
||||
const keys = Object.keys(JC_AID_NAMES);
|
||||
assert.ok(keys.length >= 45, 'entries: ' + keys.length);
|
||||
keys.forEach(k => {
|
||||
assert.match(k, /^[0-9A-F]{10,32}$/, k);
|
||||
assert.strictEqual(k.length % 2, 0, k);
|
||||
assert.ok(typeof JC_AID_NAMES[k] === 'string' && JC_AID_NAMES[k].length > 0, k);
|
||||
});
|
||||
Object.keys(JC_AID_RIDS).forEach(k => assert.match(k, /^[0-9A-F]{10}$/, k));
|
||||
});
|
||||
@@ -23,7 +23,8 @@ function extractFunc(src, name) {
|
||||
|
||||
// Extract chain builder functions and dependencies
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml',
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute'];
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute',
|
||||
'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
let code = '';
|
||||
for (const f of FNS) {
|
||||
code += extractFunc(html, f) + '\n';
|
||||
@@ -32,6 +33,10 @@ const m = html.match(/const _chains = \{\};/);
|
||||
if (m) code += m[0].replace(/^const /, 'var ') + '\n';
|
||||
const lc = html.match(/const RAM_LIFECYCLE = \{[\s\S]*?\n\};/);
|
||||
if (lc) code += lc[0].replace(/^const /, 'var ') + '\n';
|
||||
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';
|
||||
eval(code);
|
||||
code += 'var _ramCardIdx = null;\nvar _ramOpLast = null;\nvar _ramExplorerData = null;\n';
|
||||
eval(code);
|
||||
@@ -192,6 +197,20 @@ test('ramRenderExploreHtml localizes every label and button', () => {
|
||||
assert.ok(seen.includes('Load File AID / Package AID:'));
|
||||
assert.ok(seen.includes('Executable Module AIDs / Applet Class AIDs:'));
|
||||
assert.ok(!out.includes('data-l10n'), out);
|
||||
// the well-known ISD AID is annotated (GP Card Spec v2.3.1 H.1.3)
|
||||
assert.ok(out.includes('A000000151000000 <span class="text-gray-400 dark:text-slate-500">(GlobalPlatform Issuer Security Domain)</span>'), out);
|
||||
});
|
||||
|
||||
test('ramRenderExploreHtml annotates standard package AIDs, vendor AIDs stay bare', () => {
|
||||
global.t = s => s;
|
||||
const out = ramRenderExploreHtml(null, [], [], [
|
||||
{ aid: 'A0000000871005FFFFFFFF8913200000', lifecycle: '01', version: '1.0',
|
||||
moduleAids: ['A000000062010101'], sdAid: 'A0000001515350' },
|
||||
]);
|
||||
delete global.t;
|
||||
assert.ok(out.includes('(uicc.usim.toolkit)'), out);
|
||||
assert.ok(out.includes('(javacard.framework.service)'), out);
|
||||
assert.ok(out.includes('(GlobalPlatform RID)'), out);
|
||||
});
|
||||
|
||||
test('ramFmtPrivileges uses the translated (none) placeholder', () => {
|
||||
|
||||
@@ -58,6 +58,10 @@ test('scp81LogLine renders script entries', () => {
|
||||
'13 script-memory applets=4 free NV=61600 free vol=2048');
|
||||
});
|
||||
|
||||
eval(html.match(/const JC_AID_NAMES = \{[\s\S]*?\n\};/)[0].replace(/^const /, 'var '));
|
||||
eval(html.match(/const JC_AID_RIDS = \{[\s\S]*?\n\};/)[0].replace(/^const /, 'var '));
|
||||
eval(extractFunc(html, 'jcAidName'));
|
||||
eval(extractFunc(html, 'jcAidSuffix'));
|
||||
eval(extractFunc(html, 'scp81DecodeGetStatus'));
|
||||
eval(extractFunc(html, 'scp81GroupResults'));
|
||||
eval(extractFunc(html, 'scp81ResultLines'));
|
||||
@@ -105,6 +109,15 @@ test('scp81ResultLines decodes GET STATUS entries', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('scp81ResultLines annotates standard package AIDs and RID hints', () => {
|
||||
const lines = scp81ResultLines({ apdu: '80F24002024F0000', results: [
|
||||
{ rapdu: 'E30E4F08A0000001510000009F70010F', sw: '9000' },
|
||||
{ rapdu: 'E3174F07A00000015153509F7001018408A000000062010101', sw: '9000' },
|
||||
] });
|
||||
assert.strictEqual(lines[0], 'A000000151000000 (GlobalPlatform Issuer Security Domain) life=0F');
|
||||
assert.strictEqual(lines[1], 'A0000001515350 (GlobalPlatform RID) life=01 module=A000000062010101 (javacard.framework.service)');
|
||||
});
|
||||
|
||||
eval(extractFunc(html, 'scp81Ascii'));
|
||||
eval(extractFunc(html, 'scp81Bcd'));
|
||||
eval(extractFunc(html, 'scp81DecodeAdminParams'));
|
||||
|
||||
Reference in New Issue
Block a user