feat: show the CAP's required libraries, package identity and components (v3.5.16)
The Import component (JC VM spec 6.6) is now exposed by /api/cap-info and rendered in the CAP analysis box: - imports: the libraries the CAP is linked against with the export-file versions and the number of distinct constant-pool references (6.7), displayed as "name >= version" (a card resolves an import only with the same major and a minor >= the recorded one, 4.5.2). Standard names come from the AID table; each gets a family label (Oracle JavaCard / ETSI SIM 2G / ETSI UICC / 3GPP USIM-ISIM / GlobalPlatform) and, for javacard.framework, a Java Card SDK release hint derived from the local Oracle SDK kit corpus (jc211..jc305u4 exports; unknown versions stay unhinted). Vendor/applet AIDs stay bare. - Header package flags (Table 6-4: int / exports / applet package) and the optional JC 2.2 package_name (absent in all CAP 2.1 files). - components: every archive entry in load-file order with its size and share of the load file (including the Directory/Export entries capmem does not parse); the sizes sum to load_file_bytes. - The PWA box leads with "Requires: ..." when imports exist, keeps the compiled-against line (Java Card hint + CAP format), the package/applet identity, the import details (family, refs, AID) and the component breakdown in Details; a memory-only response still renders. Tests: Python +2 (imports/flags/name/components; header flags + package name) with the synthetic CAP builder extended; frontend +2 renderer cases (unknown AIDs, no-import responses) plus jcAidNorm/jcAidFamily tests; the jcAidNorm extraction added to the ram/scp81 harnesses. Help EN/RU, docs/api.md, AGENTS. 591 frontend / 473 Python green; version 3.5.16; sw simple-v269.
This commit is contained in:
@@ -31,8 +31,10 @@ function extractFunc(src, name) {
|
||||
// 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, 'jcAidNorm'));
|
||||
eval(extractFunc(html, 'jcAidName'));
|
||||
eval(extractFunc(html, 'jcAidSuffix'));
|
||||
eval(extractFunc(html, 'jcAidFamily'));
|
||||
eval(extractFunc(html, 'jcAidHtml'));
|
||||
globalThis.esc = s => s;
|
||||
|
||||
@@ -81,6 +83,24 @@ test('the plain-text and HTML suffixes name known AIDs only', () => {
|
||||
globalThis.esc = s => s;
|
||||
});
|
||||
|
||||
test('the AID family follows the RID groups (JAVACARD.md section 6)', () => {
|
||||
assert.strictEqual(jcAidFamily('A0000000620101'), 'Oracle JavaCard API');
|
||||
assert.strictEqual(jcAidFamily('A0000000090003FFFFFFFF8910710002'), 'ETSI SIM (2G) API');
|
||||
assert.strictEqual(jcAidFamily('A0000000090005FFFFFFFF8912000000'), 'ETSI UICC API');
|
||||
assert.strictEqual(jcAidFamily('A0000000871005FFFFFFFF8913200000'), '3GPP USIM/ISIM API');
|
||||
assert.strictEqual(jcAidFamily('A00000015100'), 'GlobalPlatform API');
|
||||
// vendor/applet AIDs stay unlabelled
|
||||
assert.strictEqual(jcAidFamily('D276000005AAFFCAFE0010'), '');
|
||||
assert.strictEqual(jcAidFamily(''), '');
|
||||
});
|
||||
|
||||
test('jcAidNorm validates and normalises', () => {
|
||||
assert.strictEqual(jcAidNorm('a0 00 00 00 62 01 01'), 'A0000000620101');
|
||||
assert.strictEqual(jcAidNorm('AB'), '');
|
||||
assert.strictEqual(jcAidNorm('A000000062010'), ''); // odd hex length
|
||||
assert.strictEqual(jcAidNorm(null), '');
|
||||
});
|
||||
|
||||
test('the package table is well-formed', () => {
|
||||
const keys = Object.keys(JC_AID_NAMES);
|
||||
assert.ok(keys.length >= 45, 'entries: ' + keys.length);
|
||||
|
||||
@@ -32,6 +32,12 @@ function extractFunc(src, name, asyncFn) {
|
||||
eval(extractBlock('const _capAnalysis = {', 'function capFileKey').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'capFileKey'));
|
||||
eval(extractFunc(html, 'capGateOk'));
|
||||
eval(extractBlock('const JC_AID_NAMES = {', 'const JC_AID_RIDS = {').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const JC_AID_RIDS = {', '// Normalise an AID').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const JC_FRAMEWORK_SDK = {', '// ===== TLV parsers for GET STATUS').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'jcAidNorm'));
|
||||
eval(extractFunc(html, 'jcAidName'));
|
||||
eval(extractFunc(html, 'jcAidFamily'));
|
||||
eval(extractFunc(html, 'capMemBytes'));
|
||||
eval(extractFunc(html, 'capMemHtml'));
|
||||
eval(extractFunc(html, 'capAnalyzeFile', true));
|
||||
@@ -48,6 +54,15 @@ function resetState() {
|
||||
|
||||
function memFixture() {
|
||||
return {
|
||||
cap_version: '2.1',
|
||||
package_aid: 'A0000000620101',
|
||||
package_version: '1.0',
|
||||
package_name: null,
|
||||
flags: { raw: 4, int: false, export: false, applet: true },
|
||||
applets: ['A0000000620101'],
|
||||
imports: [{ aid: 'A0000000620101', minor: 0, major: 1, refs: 6 }],
|
||||
components: [{ name: 'Header', size: 20 }, { name: 'Method', size: 2800 },
|
||||
{ name: 'ConstantPool', size: 180 }],
|
||||
code: { method_component: 2048, load_file: 3000 },
|
||||
nvram: { static_image: 12, array_init: 4, install_objects: 100, header_overhead: 12, ref_storage: 8, total: 136, runtime: 0, requirement: 3136 },
|
||||
ram: { transient_arrays: 16, runtime_transient: 0, peak_frame: 8, total: 24 },
|
||||
@@ -83,9 +98,36 @@ test('capMemHtml renders the NVRAM requirement, breakdown and warnings', () => {
|
||||
assert.ok(out.includes('Table 11-48'), out);
|
||||
assert.ok(out.includes('Estimate only'), out);
|
||||
assert.ok(out.includes('unknown opcode 0xAA'), out);
|
||||
// the required libraries with the family, reference count and the
|
||||
// compiled-against hint derived from the framework version
|
||||
assert.ok(out.includes('Requires: javacard.framework \u2265 1.0'), out);
|
||||
assert.ok(out.includes('Compiled against: Java Card 2.1.1/2.1.2 \u00b7 CAP format 2.1'), out);
|
||||
assert.ok(out.includes('javacard.framework 1.0 — Oracle JavaCard API — 6 refs — A0000000620101'), out);
|
||||
assert.ok(out.includes('Package: A0000000620101 v1.0 (applet package)'), out);
|
||||
assert.ok(out.includes('Applets: A0000000620101 (javacard.framework)'), out);
|
||||
assert.ok(out.includes('Components: Header 20 B (1%) \u00b7 Method 2.7 kB (93%) \u00b7 ConstantPool 180 B (6%)'), out);
|
||||
assert.strictEqual(capMemHtml(null), '');
|
||||
});
|
||||
|
||||
test('capMemHtml skips the import section on a response without imports', () => {
|
||||
const mem = memFixture();
|
||||
delete mem.imports;
|
||||
mem.code = { method_component: 2048 };
|
||||
delete mem.nvram.requirement;
|
||||
const out = capMemHtml(mem);
|
||||
assert.ok(!out.includes('Requires:'), out);
|
||||
assert.ok(out.includes('NVRAM requirement \u2248 2.1 kB'), out);
|
||||
});
|
||||
|
||||
test('capMemHtml keeps unknown import AIDs bare and labels the family only when known', () => {
|
||||
const mem = memFixture();
|
||||
mem.imports = [{ aid: 'A1130001180001', minor: 0, major: 1, refs: 2 }];
|
||||
const out = capMemHtml(mem);
|
||||
assert.ok(out.includes('Requires: A1130001180001 \u2265 1.0'), out);
|
||||
assert.ok(out.includes('A1130001180001 1.0 — 2 refs'), out);
|
||||
assert.ok(!out.includes('Compiled against: Java Card'), out); // no framework import
|
||||
});
|
||||
|
||||
test('capMemHtml falls back to the bytecode size on older server responses', () => {
|
||||
const mem = memFixture();
|
||||
delete mem.code.load_file;
|
||||
|
||||
@@ -24,7 +24,7 @@ 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',
|
||||
'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
let code = '';
|
||||
for (const f of FNS) {
|
||||
code += extractFunc(html, f) + '\n';
|
||||
|
||||
@@ -60,6 +60,7 @@ test('scp81LogLine renders script entries', () => {
|
||||
|
||||
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, 'jcAidNorm'));
|
||||
eval(extractFunc(html, 'jcAidName'));
|
||||
eval(extractFunc(html, 'jcAidSuffix'));
|
||||
eval(extractFunc(html, 'scp81DecodeGetStatus'));
|
||||
|
||||
Reference in New Issue
Block a user