feat: CAP compatibility test as its own operation + append-only imports (v3.6.48)
The import probe no longer lives in 'Install Package (.cap file)': a
diagnostic that can leave a LOADED-only package does not belong in the
operation meant to produce a working applet. /api/ram-install no longer
takes probe_imports; the install form's probe fieldset is gone.
New RAM operation CAP compatibility test (POST /api/cap-compat): the
server runs the format check, INSTALL [for load] and the LOAD blocks only
up to the block that completes the Import component (_cap_import_end +
_cap_compat_blocks, computed from the load file and the C4 TLV framing),
then stops - no last-block flag, no INSTALL [for install], so nothing is
committed and nothing needs deleting. The verdict (imports_ok,
boundary_block/total_blocks) states it covers the LOAD/import gate only;
the link gate stays the real install's business. Verified on the real
RemMobileID CAP: the boundary is LOAD 1/64, 2/128, 3/240 for 240/120/64 B
blocks - exactly the blocks where the live cards rejected the load.
_probe_import_versions(load_file, overrides, additions) now supports
append-only synthetic imports: +AID=version appends a new entry, the
Import component's size field and the Directory's Import size are patched,
and existing entries keep their positions (constant-pool import indices
stay valid); an AID that is already imported becomes a version override.
This is the card-capability query ('does this card export package X at
>= Y?') without building a probe applet.
PWA: new op section (CAP file + analysis + the version list + block size),
ramCompatTest with inline results and ramCompatVerdict, ramProbeParse
understands +AID=version; help EN/RU documents the operation, the two-step
bisect and the gate scope.
sw cache simple-v321; tests: 538 Python / 686 Node.
This commit is contained in:
File diff suppressed because one or more lines are too long
+2
-1
@@ -320,7 +320,8 @@
|
||||
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Operation</th><th class="text-left py-1 px-2">Description</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">Explore Card (all GP data)</td><td class="py-1 px-2">Queries GET STATUS for ISD, Applications, ELFs, and ELF Modules, plus GET DATA FF21 (Extended Card Resources: the number of installed applications and the free non-volatile/volatile memory). If a card rejects the compact listing (<code class="font-mono text-sm">P2.b2=0</code>, deprecated by GP) the queries are retried in the expanded <code class="font-mono text-sm">E3</code> form; the <strong>Fetch expanded registry details (load-file / SD associations)</strong> checkbox also requests it as an extra pass and merges the expanded-only fields (Load File AID, Security Domain AID, module lists) — it costs extra secured packets and some cards reject the expanded query, so it is off by default and remembered per browser. Results appear in an explorer view (the success line stays minimal; the counts are in the section headings) with per-item <strong>Delete</strong> buttons and the detected RAM command format (hover for what compact vs expanded means).</td></tr>
|
||||
<tr><td class="py-1 px-2">Install Package (.cap file)</td><td class="py-1 px-2">Sends a <code class="font-mono text-sm">.cap</code> file to the card via the server: INSTALL[for load] → LOAD ×N → INSTALL[for install (+make selectable)]. The load file is split into LOAD APDUs of the <strong>LOAD block size</strong> (1–240 bytes of payload, 240 by default); each secured packet is delivered as a single SMS or, when larger, as a concatenated SMS-PP download of up to 5 segments, so a large <code class="font-mono text-sm">.cap</code> simply takes several SMS. As soon as the file is selected it is validated and its <strong>memory requirements are estimated</strong> in the form: the <strong>NVRAM requirement</strong> (the code image + persistent data, the GlobalPlatform Card Spec C6+C8 style total) and the RAM estimate. The same box reports the <strong>libraries the CAP is linked against</strong> (its Import component: library name, AID and the minimum version <code class="font-mono text-sm">≥ x.y</code>, with standard names and families resolved and a Java Card SDK hint where known), the package/applet identity and the component breakdown (sizes and shares in load-file order) — grouped under the <strong>Package</strong>, <strong>Requires</strong>, <strong>Memory</strong> and <strong>Components</strong> headings. The <strong>Platform</strong> line names the minimum Java Card level the card must provide (derived from the <code class="font-mono text-sm">javacard.framework</code> import; the header <code class="font-mono text-sm">int</code> flag additionally requires int support, Java Card 2.2+) and the <strong>ETSI release</strong> the UICC API imports come from (<code class="font-mono text-sm">uicc.toolkit</code> version → REL-N, per the workspace <code class="font-mono text-sm">sim-tools</code> corpus; shown with its basis in brackets): the JCRE checks every import against its own packages when it verifies the load file (JC VM spec 4.5.2), so a card whose platform is older rejects the LOAD as soon as the <strong>Import</strong> component is complete (the step line carries the card's remote SW and names the component the block completed, e.g. <em>completes Import</em>) — a smaller LOAD block only helps when the block exceeds the card's buffer. The <strong>Import probe</strong> fieldset (diagnostic) rewrites the CAP's requested import versions before the load — all entries to 0.0, or one at a time: since the card checks that list while it verifies the load file, this shows whether it is the rejection cause and, by bisection, which package and version the card lacks. Only the two version bytes per entry change (the load file length, the component sizes and every offset stay untouched), the probe is never used for a real install, and the applied changes are reported in the result line and the server log. Two-step use: press <strong>Set all 0.0</strong> and run the install — if the LOAD now passes, the card was refusing the import list; then put one line back to its CAP value at a time (e.g. <code class="font-mono text-sm">A0000000620101=1.3</code>) and retry: the line that makes it fail again names the package the card cannot provide at that version. A corrupt or wrong-format file fails the analysis, and <strong>Execute</strong> stays disabled until a successful analysis. The estimate is informational only and is never used for installation: NVRAM is the code image plus the persistent data (the GlobalPlatform C6+C8 style total, Table 11-48 — card memory management, rounding and the registry entry are not included, and the static field image is counted in both parts); the suggested <strong>C6</strong> follows the load file, and the suggested quotas are decimal byte counts (the hex in parentheses is how the C7/C8 install parameters encode them: C6 = non-volatile code, C7 = volatile RAM, C8 = non-volatile data).</td></tr>
|
||||
<tr><td class="py-1 px-2">Install Package (.cap file)</td><td class="py-1 px-2">Sends a <code class="font-mono text-sm">.cap</code> file to the card via the server: INSTALL[for load] → LOAD ×N → INSTALL[for install (+make selectable)]. The load file is split into LOAD APDUs of the <strong>LOAD block size</strong> (1–240 bytes of payload, 240 by default); each secured packet is delivered as a single SMS or, when larger, as a concatenated SMS-PP download of up to 5 segments, so a large <code class="font-mono text-sm">.cap</code> simply takes several SMS. As soon as the file is selected it is validated and its <strong>memory requirements are estimated</strong> in the form: the <strong>NVRAM requirement</strong> (the code image + persistent data, the GlobalPlatform Card Spec C6+C8 style total) and the RAM estimate. The same box reports the <strong>libraries the CAP is linked against</strong> (its Import component: library name, AID and the minimum version <code class="font-mono text-sm">≥ x.y</code>, with standard names and families resolved and a Java Card SDK hint where known), the package/applet identity and the component breakdown (sizes and shares in load-file order) — grouped under the <strong>Package</strong>, <strong>Requires</strong>, <strong>Memory</strong> and <strong>Components</strong> headings. The <strong>Platform</strong> line names the minimum Java Card level the card must provide (derived from the <code class="font-mono text-sm">javacard.framework</code> import; the header <code class="font-mono text-sm">int</code> flag additionally requires int support, Java Card 2.2+) and the <strong>ETSI release</strong> the UICC API imports come from (<code class="font-mono text-sm">uicc.toolkit</code> version → REL-N, per the workspace <code class="font-mono text-sm">sim-tools</code> corpus; shown with its basis in brackets): the JCRE checks every import against its own packages when it verifies the load file (JC VM spec 4.5.2), so a card whose platform is older rejects the LOAD as soon as the <strong>Import</strong> component is complete (the step line carries the card's remote SW and names the component the block completed, e.g. <em>completes Import</em>) — a smaller LOAD block only helps when the block exceeds the card's buffer. A corrupt or wrong-format file fails the analysis, and <strong>Execute</strong> stays disabled until a successful analysis. The estimate is informational only and is never used for installation: NVRAM is the code image plus the persistent data (the GlobalPlatform C6+C8 style total, Table 11-48 — card memory management, rounding and the registry entry are not included, and the static field image is counted in both parts); the suggested <strong>C6</strong> follows the load file, and the suggested quotas are decimal byte counts (the hex in parentheses is how the C7/C8 install parameters encode them: C6 = non-volatile code, C7 = volatile RAM, C8 = non-volatile data).</td></tr>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">CAP compatibility test</td><td class="py-1 px-2">Answers one question without installing anything: <strong>can this card satisfy the CAP's import list?</strong> The server runs the format check, INSTALL[for load] and the LOAD blocks only up to the block that completes the <strong>Import</strong> component — the point where the JCRE verifies the import list — then stops: no last-block flag and no INSTALL[for install], so nothing is committed and nothing has to be deleted afterwards. The <strong>Import versions to test</strong> list is pre-filled from the CAP analysis: <code class="font-mono text-sm">AID=version</code> rewrites an import's requested version, <code class="font-mono text-sm">+AID=version</code> appends a synthetic import (a card-capability query: the LOAD passes only if the card exports that package at ≥ that version), and <strong>Set all 0.0</strong> drops every requirement. Two-step use: press Set all 0.0 — if the test passes, the import list was the blocker; then restore one line at a time to find the package and version the card lacks. The verdict states it covers the <strong>LOAD/import gate only</strong>: the link gate (whether the applet's actual API calls exist) is what a real install checks at INSTALL[for install].</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
+109
-58
@@ -695,6 +695,7 @@
|
||||
<select id="ram-op" onchange="ramOpChanged()" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800">
|
||||
<option value="explore" data-l10n="Explore Card (all GP data)">Explore Card (all GP data)</option>
|
||||
<option value="install-cap" data-l10n="Install Package (.cap file)">Install Package (.cap file)</option>
|
||||
<option value="compat" data-l10n="CAP compatibility test">CAP compatibility test</option>
|
||||
<option value="install-app" data-l10n="INSTALL [for install] (loaded package)">INSTALL [for install] (loaded package)</option>
|
||||
<option value="make-selectable" data-l10n="INSTALL [for make selectable]">INSTALL [for make selectable]</option>
|
||||
</select>
|
||||
@@ -704,32 +705,30 @@
|
||||
|
||||
<label class="flex items-center gap-1 mb-3 text-sm text-gray-700 dark:text-slate-300" data-l10n-title="Runs one extra expanded query per listing (P2=02, tag list 4F 9F70 C5 C4 CC CE EA). The compact listing carries AID/lifecycle/privileges/version only; the expanded pass adds the Load File AID, Security Domain AID and module lists. It costs extra secured packets and some cards reject it - the Explore already uses it automatically when the compact listing returns nothing."><input type="checkbox" id="ram-expanded-details" class="rounded border-gray-300 dark:border-slate-600" onchange="ramExpandedDetailsChanged()"> <span data-l10n="Fetch expanded registry details (load-file / SD associations)">Fetch expanded registry details (load-file / SD associations)</span></label>
|
||||
|
||||
<div id="ram-cap-rows" class="hidden mb-3">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="CAP file (max 48 kB)">CAP file (max 48 kB)</label>
|
||||
<input type="file" id="ram-cap-file" accept=".cap,.zip" onchange="ramCapFileChanged()" class="w-full text-sm text-gray-600 dark:text-slate-300">
|
||||
<fieldset id="ram-cap-info" class="hidden mt-2 border border-gray-200 dark:border-slate-700 rounded p-3 text-xs text-gray-500 dark:text-slate-400">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="CAP details">CAP details</legend>
|
||||
<div id="ram-cap-body"></div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div id="ram-compat-rows" class="hidden mb-3">
|
||||
<fieldset class="border border-gray-200 dark:border-slate-700 rounded p-3" data-l10n-title="Runs the load only as far as the block that completes the CAP's Import component: the card checks the import list there, so its answer says whether the card can satisfy it. Nothing is installed and nothing is committed - the link gate is the real install's business. Lines are AID=version (rewrites an import) or +AID=version (appends a synthetic import, to query the card for a package the CAP does not use).">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Import versions to test">Import versions to test</legend>
|
||||
<textarea id="ram-probe-versions" rows="4" spellcheck="false" oninput="this.dataset.manual='1'" class="w-full font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-800" placeholder="AID=version per line (+AID=version appends)"></textarea>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<button type="button" onclick="ramProbeAllZero()" data-l10n-title="Sets every AID=version line to 0.0; +AID=version lines keep their version." class="px-2 py-0.5 text-xs rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-l10n="Set all 0.0">Set all 0.0</button>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 dark:text-slate-500 mt-1" data-l10n="The card checks the CAP's import list while it loads, so a rejection at the Import component usually means it cannot satisfy that list.">The card checks the CAP's import list while it loads, so a rejection at the Import component usually means it cannot satisfy that list.</div>
|
||||
<div class="text-xs text-gray-400 dark:text-slate-500" data-l10n="Press 'Set all 0.0' first: if the test now passes, the list was the cause; then restore one line at a time to find the package and version the card lacks. A +AID=version line asks the card about a package the CAP does not use.">Press 'Set all 0.0' first: if the test now passes, the list was the cause; then restore one line at a time to find the package and version the card lacks. A +AID=version line asks the card about a package the CAP does not use.</div>
|
||||
</fieldset>
|
||||
<label class="block mb-1 mt-2 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="LOAD block size (1..240, default 240)">LOAD block size (1..240, default 240)</label>
|
||||
<input id="ram-compat-block" type="number" min="1" max="240" class="w-32 font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="240">
|
||||
</div>
|
||||
<div id="ram-install-params" class="hidden mb-3">
|
||||
<div id="ram-cap-rows">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="CAP file (max 48 kB)">CAP file (max 48 kB)</label>
|
||||
<input type="file" id="ram-cap-file" accept=".cap,.zip" onchange="ramCapFileChanged()" class="w-full text-sm text-gray-600 dark:text-slate-300">
|
||||
<fieldset id="ram-cap-info" class="hidden mt-2 border border-gray-200 dark:border-slate-700 rounded p-3 text-xs text-gray-500 dark:text-slate-400">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="CAP details">CAP details</legend>
|
||||
<div id="ram-cap-body"></div>
|
||||
</fieldset>
|
||||
<fieldset class="mt-2 border border-gray-200 dark:border-slate-700 rounded p-3">
|
||||
<legend class="px-1 text-xs font-medium text-gray-500 dark:text-slate-400" data-l10n="Import probe (diagnostic)" data-l10n-title="Diagnostic only: rewrites the two version bytes per import in the load file (same length); never used for a real install.">Import probe (diagnostic)</legend>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-slate-300 cursor-pointer" data-l10n-title="Diagnostic only: rewrites the two version bytes per import in the load file (same length); never used for a real install.">
|
||||
<input id="ram-probe-enable" type="checkbox" class="rounded border-gray-300 dark:border-slate-600 dark:bg-slate-800" onchange="ramProbeChanged()">
|
||||
<span data-l10n="Enable import probe">Enable import probe</span>
|
||||
</label>
|
||||
<div id="ram-probe-body" class="hidden mt-2">
|
||||
<textarea id="ram-probe-versions" rows="4" spellcheck="false" oninput="this.dataset.manual='1'" class="w-full font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-800" placeholder="AID=version per line"></textarea>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<button type="button" onclick="ramProbeAllZero()" data-l10n-title="Sets every AID=version line to 0.0." class="px-2 py-0.5 text-xs rounded bg-gray-200 dark:bg-slate-700 text-gray-700 dark:text-slate-300 hover:bg-gray-300 dark:hover:bg-slate-600" data-l10n="Set all 0.0">Set all 0.0</button>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 dark:text-slate-500 mt-1" data-l10n="The card checks the CAP's import list while it loads, so a LOAD rejection at the Import component usually means it cannot satisfy that list.">The card checks the CAP's import list while it loads, so a LOAD rejection at the Import component usually means it cannot satisfy that list.</div>
|
||||
<div class="text-xs text-gray-400 dark:text-slate-500" data-l10n="Press 'Set all 0.0' first: if the LOAD now passes, the list was the cause; then restore one line at a time to find the package and version the card lacks.">Press 'Set all 0.0' first: if the LOAD now passes, the list was the cause; then restore one line at a time to find the package and version the card lacks.</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300 mt-2" data-l10n="SD AID (empty = ISD)">SD AID (empty = ISD)</label>
|
||||
<input id="ram-sd-aid" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="A000000003000000" maxlength="32">
|
||||
</div>
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="SD AID (empty = ISD)">SD AID (empty = ISD)</label>
|
||||
<input id="ram-sd-aid" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="A000000003000000" maxlength="32">
|
||||
<div id="ram-app-rows" class="hidden">
|
||||
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Load File AID / Package AID (hex)">Load File AID / Package AID (hex)</label>
|
||||
<input id="ram-app-loadfile" class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800" placeholder="F0414C46416101" maxlength="32">
|
||||
@@ -1746,7 +1745,7 @@
|
||||
// ===== Version =====
|
||||
// Single source of truth for the PWA version: shown in the header and used
|
||||
// by the server version check in pysimConnect().
|
||||
const SIMPLE_VERSION = '3.6.47';
|
||||
const SIMPLE_VERSION = '3.6.48';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -8294,15 +8293,17 @@ function ramOpChanged() {
|
||||
if (_ramOpLast !== null && op !== _ramOpLast) ramClearResults();
|
||||
_ramOpLast = op;
|
||||
const isCap = op === 'install-cap';
|
||||
const isCompat = op === 'compat';
|
||||
const isApp = op === 'install-app' || op === 'make-selectable';
|
||||
const setHidden = (id, hidden) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.classList.toggle('hidden', hidden);
|
||||
};
|
||||
setHidden('ram-install-params', !(isCap || isApp));
|
||||
setHidden('ram-cap-rows', !isCap);
|
||||
setHidden('ram-cap-rows', !(isCap || isCompat));
|
||||
setHidden('ram-compat-rows', !isCompat);
|
||||
setHidden('ram-app-rows', !isApp);
|
||||
// The Execute button's .cap gate only applies to the install-cap op.
|
||||
// The Execute button's .cap gate applies to the install-cap and compat ops.
|
||||
if (typeof pysimApplyAvailability === 'function') pysimApplyAvailability();
|
||||
}
|
||||
|
||||
@@ -8427,7 +8428,8 @@ function capFileKey(file) {
|
||||
function capGateOk(kind) {
|
||||
if (kind === 'ram') {
|
||||
const op = document.getElementById('ram-op');
|
||||
if (op && op.value !== 'install-cap') return true; // only the install op is gated
|
||||
// the install and the compatibility test both need the analysed CAP
|
||||
if (op && op.value !== 'install-cap' && op.value !== 'compat') return true;
|
||||
}
|
||||
const st = _capAnalysis[kind];
|
||||
return !!(st && st.status === 'ok');
|
||||
@@ -9494,25 +9496,29 @@ function ramInstallFailHint(data, mem) {
|
||||
// rewriting the requested versions shows whether that list is what it refuses
|
||||
// - and, by lowering one entry at a time, which package and version it lacks.
|
||||
|
||||
// 'AID=version' per line (or 'all=x.y'); blank/comment lines are ignored.
|
||||
// Pure: returns {map} or {error}.
|
||||
// 'AID=version' per line ('all=x.y' allowed) rewrites an import; a leading
|
||||
// '+' appends a synthetic import (a card-capability query); blank/comment
|
||||
// lines are ignored. Pure: returns {map, additions} or {error}.
|
||||
function ramProbeParse(text) {
|
||||
const out = {};
|
||||
const map = {};
|
||||
const additions = [];
|
||||
for (const rawLine of String(text || '').split('\n')) {
|
||||
const line = rawLine.split('#')[0].trim();
|
||||
if (!line) continue;
|
||||
const m = /^([0-9a-fA-F]{2,32}|all)\s*=\s*(\d{1,3})\.(\d{1,3})$/.exec(line);
|
||||
const m = /^(\+?)([0-9a-fA-F]{2,32}|all)\s*=\s*(\d{1,3})\.(\d{1,3})$/.exec(line);
|
||||
if (!m) return { error: t('Invalid line:') + ' ' + line };
|
||||
const aid = m[1].toLowerCase() === 'all' ? 'all' : m[1].toUpperCase();
|
||||
out[aid] = m[2] + '.' + m[3];
|
||||
const ver = m[3] + '.' + m[4];
|
||||
if (m[1]) {
|
||||
if (m[2].toLowerCase() === 'all') return { error: t('Invalid line:') + ' ' + line };
|
||||
additions.push({ aid: m[2].toUpperCase(), version: ver });
|
||||
} else {
|
||||
map[m[2].toLowerCase() === 'all' ? 'all' : m[2].toUpperCase()] = ver;
|
||||
}
|
||||
}
|
||||
if (!Object.keys(out).length) return { error: t('No import versions given') };
|
||||
return { map: out };
|
||||
}
|
||||
|
||||
function ramProbeChanged() {
|
||||
const on = document.getElementById('ram-probe-enable').checked;
|
||||
document.getElementById('ram-probe-body').classList.toggle('hidden', !on);
|
||||
if (!Object.keys(map).length && !additions.length) {
|
||||
return { error: t('No import versions given') };
|
||||
}
|
||||
return { map: map, additions: additions };
|
||||
}
|
||||
|
||||
// Prefill from the analysed CAP's import list unless the user edited it.
|
||||
@@ -9528,25 +9534,66 @@ function ramProbeAllZero() {
|
||||
if (!el) return;
|
||||
el.value = String(el.value || '').split('\n').map(line => {
|
||||
const m = /^([0-9a-fA-F]{2,32}|all)\s*=/.exec(line.trim());
|
||||
return m ? m[1] + '=0.0' : line;
|
||||
return m ? m[1] + '=0.0' : line; // '+AID=' lines keep their version
|
||||
}).join('\n');
|
||||
el.dataset.manual = '1';
|
||||
}
|
||||
|
||||
// Verdict line for the CAP compatibility test (pure). The test covers the
|
||||
// LOAD/import gate only - the card checks the import list while it verifies
|
||||
// the load file; the link gate is what a real install checks later.
|
||||
function ramCompatVerdict(data) {
|
||||
if (!data) return '';
|
||||
const scope = t('tested through LOAD') + ' ' + (data.boundary_block || '?') +
|
||||
'/' + (data.total_blocks || '?');
|
||||
if (data.imports_ok) return t('Import list accepted') + ' \u2014 ' + scope;
|
||||
return t('Import list rejected') + ' \u2014 ' + scope +
|
||||
(data.error ? ' \u00b7 ' + data.error : '');
|
||||
}
|
||||
|
||||
// CAP compatibility test: run the load only as far as the Import component's
|
||||
// end and report whether the card accepted the import list. Nothing is
|
||||
// installed or committed (the install operation keeps the CAP's own versions).
|
||||
async function ramCompatTest(sp) {
|
||||
const fileInput = document.getElementById('ram-cap-file');
|
||||
const file = fileInput.files[0];
|
||||
if (!file) { alert(t('Select a .cap file')); return; }
|
||||
if (file.size > 48 * 1024) { alert(t('CAP file exceeds 48 kB limit')); return; }
|
||||
const parsed = ramProbeParse(document.getElementById('ram-probe-versions').value);
|
||||
if (parsed.error) { alert(parsed.error); return; }
|
||||
|
||||
ramClearResults();
|
||||
ramShowProgress(t('Sending to server...'));
|
||||
const body = {
|
||||
cap_hex: await ramReadFileHex(file),
|
||||
probe_imports: parsed.map, probe_additions: parsed.additions,
|
||||
spi1: sp.spi1, spi2: '01', kic: sp.kic, kid: sp.kid,
|
||||
tar: sp.tar, cntr: sp.cntr, kicKey: sp.kicKey, kidKey: sp.kidKey,
|
||||
};
|
||||
const bs = parseInt(document.getElementById('ram-compat-block').value || '', 10);
|
||||
if (bs >= 1 && bs <= 240) body.load_block_size = bs;
|
||||
|
||||
const data = await pysimFetch('/api/cap-compat', body);
|
||||
ramHideProgress();
|
||||
if (data.final_cntr) ramSaveCntr(data.final_cntr);
|
||||
const comps = _capAnalysis.ram.memory && _capAnalysis.ram.memory.components;
|
||||
let txt = '';
|
||||
(data.steps || []).forEach((s, idx) => { txt += ramStepLine(s, idx, comps, data.load_block_size) + '\n'; });
|
||||
const stepsEl = document.getElementById('ram-steps');
|
||||
stepsEl.classList.remove('hidden');
|
||||
stepsEl.textContent = txt;
|
||||
const resultEl = document.getElementById('ram-result');
|
||||
resultEl.textContent = ramCompatVerdict(data);
|
||||
resultEl.classList.remove('hidden', 'text-green-600', 'text-red-600');
|
||||
resultEl.classList.add(data.imports_ok ? 'text-green-600' : 'text-red-600');
|
||||
}
|
||||
|
||||
async function ramInstallCap(sp) {
|
||||
const fileInput = document.getElementById('ram-cap-file');
|
||||
const file = fileInput.files[0];
|
||||
if (!file) { alert(t('Select a .cap file')); return; }
|
||||
if (file.size > 48 * 1024) { alert(t('CAP file exceeds 48 kB limit')); return; }
|
||||
|
||||
// Import probe (diagnostic): validated before the modal opens
|
||||
let probe = null;
|
||||
if (document.getElementById('ram-probe-enable').checked) {
|
||||
const parsed = ramProbeParse(document.getElementById('ram-probe-versions').value);
|
||||
if (parsed.error) { alert(parsed.error); return; }
|
||||
probe = parsed.map;
|
||||
}
|
||||
|
||||
const capPkg = (_capAnalysis.ram.memory && _capAnalysis.ram.memory.package_aid) || '';
|
||||
ramOpModalBegin(t('Installing package') + (capPkg ? ' \u2014 ' + capPkg : ''));
|
||||
ramOpModalStatus(t('Reading CAP file...'));
|
||||
@@ -9582,7 +9629,6 @@ async function ramInstallCap(sp) {
|
||||
if (!(n >= 1 && n <= 240)) { alert(t('LOAD block size must be 1..240')); return; }
|
||||
body.load_block_size = n;
|
||||
}
|
||||
if (probe) body.probe_imports = probe;
|
||||
|
||||
const data = await pysimFetch('/api/ram-install', body);
|
||||
|
||||
@@ -9693,6 +9739,7 @@ async function ramExecute() {
|
||||
try {
|
||||
if (op === 'explore') await ramExplore(sp);
|
||||
else if (op === 'install-cap') await ramInstallCap(sp);
|
||||
else if (op === 'compat') await ramCompatTest(sp);
|
||||
else if (op === 'install-app' || op === 'make-selectable') await ramInstallApp(sp, op);
|
||||
} catch (e) {
|
||||
const resultEl = document.getElementById('ram-result');
|
||||
@@ -17474,13 +17521,17 @@ const LANG_RU = {
|
||||
'Install OK': 'Установка OK',
|
||||
'Install FAILED at step:': 'Установка не удалась на шаге:',
|
||||
'Hint:': 'Подсказка:',
|
||||
'Import probe (diagnostic)': 'Проба импортов (диагностика)',
|
||||
'Enable import probe': 'Включить пробу импортов',
|
||||
'Set all 0.0': 'Все 0.0',
|
||||
'Diagnostic only: rewrites the two version bytes per import in the load file (same length); never used for a real install.': 'Только диагностика: переписывает два байта версии у каждого импорта в load-файле (длина не меняется); для настоящей установки не используется.',
|
||||
'Sets every AID=version line to 0.0.': 'Заменяет все строки AID=version на 0.0.',
|
||||
"The card checks the CAP's import list while it loads, so a LOAD rejection at the Import component usually means it cannot satisfy that list.": 'Карта проверяет список импортов CAP при загрузке, поэтому отказ LOAD на компоненте Import обычно означает, что она не может удовлетворить этот список.',
|
||||
"Press 'Set all 0.0' first: if the LOAD now passes, the list was the cause; then restore one line at a time to find the package and version the card lacks.": 'Сначала нажмите «Все 0.0»: если LOAD теперь проходит, причина была в списке; затем возвращайте по одной строке, чтобы найти пакет и версию, которых не хватает карте.',
|
||||
'CAP compatibility test': 'Проверка совместимости CAP',
|
||||
'Import versions to test': 'Версии импортов для проверки',
|
||||
"Runs the load only as far as the block that completes the CAP's Import component: the card checks the import list there, so its answer says whether the card can satisfy it. Nothing is installed and nothing is committed - the link gate is the real install's business. Lines are AID=version (rewrites an import) or +AID=version (appends a synthetic import, to query the card for a package the CAP does not use).": 'Выполняет загрузку только до блока, завершающего компонент Import CAP: именно там карта проверяет список импортов, поэтому её ответ показывает, может ли она их удовлетворить. Ничего не устанавливается и не фиксируется — проверка связывания остаётся за настоящей установкой. Строки: AID=version (переписать импорт) или +AID=version (добавить синтетический импорт, чтобы запросить у карты пакет, который CAP не использует).',
|
||||
'Sets every AID=version line to 0.0; +AID=version lines keep their version.': 'Заменяет все строки AID=version на 0.0; строки +AID=version сохраняют свою версию.',
|
||||
"The card checks the CAP's import list while it loads, so a rejection at the Import component usually means it cannot satisfy that list.": 'Карта проверяет список импортов CAP при загрузке, поэтому отказ на компоненте Import обычно означает, что она не может удовлетворить этот список.',
|
||||
"Press 'Set all 0.0' first: if the test now passes, the list was the cause; then restore one line at a time to find the package and version the card lacks. A +AID=version line asks the card about a package the CAP does not use.": 'Сначала нажмите «Все 0.0»: если проверка проходит, причина была в списке; затем возвращайте по одной строке, чтобы найти пакет и версию, которых не хватает карте. Строка +AID=version спрашивает карту о пакете, который CAP не использует.',
|
||||
'LOAD block size (1..240, default 240)': 'Размер блока LOAD (1..240, по умолчанию 240)',
|
||||
'Import list accepted': 'Список импортов принят',
|
||||
'Import list rejected': 'Список импортов отклонён',
|
||||
'tested through LOAD': 'проверено до LOAD',
|
||||
'Invalid line:': 'Некорректная строка:',
|
||||
'No import versions given': 'Версии импортов не заданы',
|
||||
'probe': 'проба',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v320';
|
||||
const CACHE = 'simple-v321';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -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', 'ramStepComponents', 'ramInstallFailHint', 'ramProbeParse', 'ramExpandedDetailsInit', 'ramExpandedDetailsChanged', 'ramGetStatusApdu', 'ramDeleteApdu',
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramStepComponents', 'ramInstallFailHint', 'ramProbeParse', 'ramCompatVerdict', 'ramExpandedDetailsInit', 'ramExpandedDetailsChanged', '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',
|
||||
@@ -789,6 +789,31 @@ test('ramProbeParse reads AID=version lines', () => {
|
||||
assert.ok(ramProbeParse('A0000000620101=x.y').error);
|
||||
assert.ok(ramProbeParse('').error);
|
||||
assert.ok(ramProbeParse(' \n# only comments\n').error);
|
||||
// '+AID=version' appends a synthetic import (a card-capability query)
|
||||
r = ramProbeParse('+A0000000090005FFFFFFFF8912000000=1.11');
|
||||
assert.deepStrictEqual(r.map, {});
|
||||
assert.deepStrictEqual(r.additions,
|
||||
[{ aid: 'A0000000090005FFFFFFFF8912000000', version: '1.11' }]);
|
||||
r = ramProbeParse('A0000000620101=0.0\n+0102030405=1.0');
|
||||
assert.deepStrictEqual(r.map, { 'A0000000620101': '0.0' });
|
||||
assert.strictEqual(r.additions.length, 1);
|
||||
assert.ok(ramProbeParse('+all=1.0').error);
|
||||
});
|
||||
|
||||
test('ramCompatVerdict reports the import-gate verdict', () => {
|
||||
globalThis.t = s => s;
|
||||
const ok = ramCompatVerdict({ imports_ok: true, boundary_block: 3, total_blocks: 240 });
|
||||
assert.strictEqual(ok, 'Import list accepted \u2014 tested through LOAD 3/240');
|
||||
const bad = ramCompatVerdict({ imports_ok: false, boundary_block: 3, total_blocks: 240,
|
||||
error: 'remote SW 6438' });
|
||||
assert.ok(bad.startsWith('Import list rejected \u2014 tested through LOAD 3/240'), bad);
|
||||
assert.ok(bad.includes('remote SW 6438'), bad);
|
||||
assert.strictEqual(ramCompatVerdict(null), '');
|
||||
});
|
||||
|
||||
test('ramExecute dispatches the compatibility test op', () => {
|
||||
const src = extractFunc(html, 'ramExecute');
|
||||
assert.ok(/op === 'compat'/.test(src), 'the compat op must be dispatched');
|
||||
});
|
||||
|
||||
test('ramRenderExploreHtml counts apps and ELFs in the section headings', () => {
|
||||
|
||||
Reference in New Issue
Block a user