fix: late-PoR handler on every path, one poll per packet, LOAD-step components (v3.6.37)
_send_envelope defined its SEND SHORT MESSAGE capture handler inside the 91xx branch, but the 'STATUS poll (PoR not captured)' path used it: a packet whose ENVELOPE answered 9000 and whose late PoR arrived on the STATUS poll raised UnboundLocalError, returned 500 and aborted the RAM operation mid-packet with the card's command left pending (live 2026-09-29, 120-byte LOAD blocks). The handler is now defined for every path, and the late-PoR poll runs only after a packet's last segment (poll_status from _send_secured_packet) - a concatenated download makes no spurious mid-packet STATUS exchanges. The PWA annotates every LOAD step with the CAP components its block covers (ramStepComponents: 'completes Import' / 'inside Method' / 'Header...Import') and ramInstallFailHint names the component the failing block completed - the live import-verification rejection lands in the block completing Import (6438/6985 at both 240 B and 64 B blocks). sw cache simple-v310; tests: 509 Python / 672 Node.
This commit is contained in:
@@ -22,7 +22,7 @@ function extractFunc(src, name) {
|
||||
}
|
||||
|
||||
// Extract chain builder functions and dependencies
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramInstallFailHint', 'ramGetStatusApdu', 'ramDeleteApdu',
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramStepComponents', 'ramInstallFailHint', 'ramGetStatusApdu', 'ramDeleteApdu',
|
||||
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
|
||||
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'parseTLV', '_parseE3Entry',
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
|
||||
@@ -390,6 +390,46 @@ test('ramStepLine shows the PoR verdict and the remote status word', () => {
|
||||
assert.ok(noPor.startsWith('\u2705') && noPor.includes('no PoR'), noPor);
|
||||
});
|
||||
|
||||
test('ramStepComponents maps a LOAD step to the CAP components it covers', () => {
|
||||
// RemMobileID-like layout: sizes include each component's 3-byte header
|
||||
const comps = [
|
||||
{ name: 'Header', size: 20 }, { name: 'Directory', size: 34 },
|
||||
{ name: 'Applet', size: 15 }, { name: 'Import', size: 101 },
|
||||
{ name: 'ConstantPool', size: 1121 }, { name: 'Method', size: 8506 },
|
||||
];
|
||||
// 240 B block 1 (0..239) completes the Import component (its end is 170)
|
||||
let sc = ramStepComponents('LOAD (1/64)', comps, 240);
|
||||
assert.deepStrictEqual(sc.completed, ['Header', 'Directory', 'Applet', 'Import']);
|
||||
assert.deepStrictEqual(sc.names.slice(0, 2), ['Header', 'Directory']);
|
||||
// 64 B blocks: 1 completes Header/Directory, 3 completes Import
|
||||
assert.deepStrictEqual(ramStepComponents('LOAD (1/240)', comps, 64).completed, ['Header', 'Directory']);
|
||||
sc = ramStepComponents('LOAD (2/240)', comps, 64);
|
||||
assert.deepStrictEqual(sc.names, ['Applet', 'Import']);
|
||||
assert.deepStrictEqual(sc.completed, ['Applet']);
|
||||
sc = ramStepComponents('LOAD (3/240)', comps, 64);
|
||||
assert.deepStrictEqual(sc.names, ['Import', 'ConstantPool']);
|
||||
assert.deepStrictEqual(sc.completed, ['Import']);
|
||||
// other step names and missing data produce nothing
|
||||
assert.strictEqual(ramStepComponents('INSTALL [for load]', comps, 64), null);
|
||||
assert.strictEqual(ramStepComponents('LOAD (1/64)', comps, 0), null);
|
||||
assert.strictEqual(ramStepComponents('LOAD (1/64)', null, 64), null);
|
||||
});
|
||||
|
||||
test('ramStepLine annotates a LOAD step with the completing component', () => {
|
||||
globalThis.t = s => s;
|
||||
globalThis.lookupSw = () => '';
|
||||
const comps = [{ name: 'Header', size: 20 }, { name: 'Import', size: 101 },
|
||||
{ name: 'Method', size: 8506 }];
|
||||
const line = ramStepLine({ name: 'LOAD (1/64)', por_status: 'por_ok', por_sw: '9000' }, 0, comps, 240);
|
||||
assert.ok(line.includes('completes Import'), line);
|
||||
// block 2 of 40 B blocks (40..79) lies strictly inside Import
|
||||
const inside = ramStepLine({ name: 'LOAD (2/64)', por_status: 'por_ok', por_sw: '9000' }, 1, comps, 40);
|
||||
assert.ok(inside.includes('inside Import'), inside);
|
||||
// without a component list the line is unchanged
|
||||
const plain = ramStepLine({ name: 'LOAD (1/64)', por_status: 'por_ok', por_sw: '9000' }, 0);
|
||||
assert.ok(!plain.includes('completes') && !plain.includes('inside'), plain);
|
||||
});
|
||||
|
||||
test('ramInstallFailHint names the CAP import requirement for a rejected LOAD', () => {
|
||||
globalThis.t = s => s;
|
||||
const failedLoad = {
|
||||
@@ -408,6 +448,14 @@ test('ramInstallFailHint names the CAP import requirement for a rejected LOAD',
|
||||
assert.strictEqual(ramInstallFailHint(
|
||||
{ success: false, failed_step: 2, steps: [{ name: 'INSTALL [for load]' }, { name: 'INSTALL [for install]' }] }, null), '');
|
||||
assert.strictEqual(ramInstallFailHint({ success: true, failed_step: 1, steps: [] }, null), '');
|
||||
// with the CAP analysis the completing component of the failing block is
|
||||
// named (the card check lands on the component boundary)
|
||||
const comps = [{ name: 'Header', size: 20 }, { name: 'Import', size: 101 }];
|
||||
const withBoundary = ramInstallFailHint(
|
||||
{ success: false, failed_step: 2, load_block_size: 64,
|
||||
steps: [{ name: 'INSTALL [for load]' }, { name: 'LOAD (2/240)', por_sw: '6438' }] },
|
||||
{ requires_java_card: '2.2.2', components: comps });
|
||||
assert.ok(withBoundary.startsWith('the failed step completes Import. '), withBoundary);
|
||||
});
|
||||
|
||||
test('ramOpChanged clears the executed status only on a real op change', () => {
|
||||
|
||||
Reference in New Issue
Block a user