feat: CAP platform requirement + LOAD rejection hint (v3.6.36)
capmem derives the minimum platform from the CAP's Import component
(javacard.framework -> Java Card level via the SDK-kit corpus, int flag)
and /api/cap-info exposes it; the CAP details show it as a Platform row
('Java Card >= 2.2.2 - CAP format 2.1 - int support').
A failed RAM install at a LOAD / INSTALL [for load] step now appends a
hint naming the import requirement and the smaller-block fallback
(ramInstallFailHint, pure): JC VM spec 4.5.2 makes every import a
'>= version' demand on the card's own packages checked while the load
file is verified - live 2026-09-29, a CAP requiring Java Card 2.2.2 was
rejected exactly at the block containing the end of Import (6438 at
240 B blocks, 6985 at 64 B), not at a step count.
sw cache simple-v309; tests: 507 Python / 670 Node.
This commit is contained in:
@@ -321,7 +321,7 @@
|
||||
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Операция</th><th class="text-left py-1 px-2">Описание</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700"><td class="py-1 px-2">Обзор карты (все данные GP)</td><td class="py-1 px-2">Запрос GET STATUS для ISD, приложений, ELF и модулей ELF, а также GET DATA FF21 (расширенные ресурсы карты: число установленных приложений и свободная энергонезависимая/энергозависимая память). Если карта отвергает компактный формат листинга (<code class="font-mono text-sm">P2.b2=0</code>, устаревший по GP), запросы повторяются в расширенном формате <code class="font-mono text-sm">E3</code>; флажок <strong>Запрашивать расширенные данные реестра (E3)</strong> дополнительно запрашивает их и объединяет расширенные поля (привилегии, связи ELF/SD, списки модулей). Результаты отображаются в обзоре с кнопками <strong>Удалить</strong> для каждого элемента и определённым форматом команд RAM.</td></tr>
|
||||
<tr><td class="py-1 px-2">Установка пакета (.cap файл)</td><td class="py-1 px-2">Отправка <code class="font-mono text-sm">.cap</code> файла на карту через сервер: INSTALL[for load] → LOAD ×N → INSTALL[for install (+make selectable)]. Load-файл делится на LOAD APDU размера <strong>размер блока LOAD</strong> (1–240 байт полезной нагрузки, по умолчанию 240); каждый защищённый пакет доставляется одним SMS или, если он больше, конкатенированной SMS-PP загрузкой до 5 сегментов, так что большой <code>.cap</code> просто занимает несколько SMS. Сразу после выбора файл проверяется и в форме показывается <strong>оценка требований к памяти</strong>: <strong>требование NVRAM</strong> (образ кода + постоянные данные, сумма в стиле C6+C8 из GP Card Spec) и оценка RAM. Там же показаны <strong>библиотеки, с которыми слинкован CAP</strong> (его компонент Import: имя библиотеки, AID и минимальная версия <code class="font-mono text-sm">≥ x.y</code> — имена стандартных библиотек и семейства подставляются, а где известно, даётся подсказка о версии Java Card), идентификаторы пакета и апплетов и разбивка по компонентам (размеры и доли в порядке load-файла) — всё сгруппировано по разделам <strong>Package</strong>, <strong>Requires</strong>, <strong>Memory</strong> и <strong>Components</strong>. Повреждённый файл не проходит анализ, и <strong>Execute</strong> остаётся недоступной до успешного анализа. Оценка носит справочный характер и никогда не используется при установке: NVRAM — это образ кода плюс постоянные данные (сумма в стиле C6+C8 по GP Table 11-48; управление памятью карты, округление и запись в реестре не учитываются, а образ статических полей входит в обе части); рекомендуемый <strong>C6</strong> следует за load-файлом, а рекомендуемые квоты — это десятичные значения в байтах (шестнадцатеричное значение в скобках — это кодирование параметров установки C7/C8: C6 — энергонезависимый код, C7 — энергозависимая RAM, C8 — энергонезависимые данные).</td></tr>
|
||||
<tr><td class="py-1 px-2">Установка пакета (.cap файл)</td><td class="py-1 px-2">Отправка <code class="font-mono text-sm">.cap</code> файла на карту через сервер: INSTALL[for load] → LOAD ×N → INSTALL[for install (+make selectable)]. Load-файл делится на LOAD APDU размера <strong>размер блока LOAD</strong> (1–240 байт полезной нагрузки, по умолчанию 240); каждый защищённый пакет доставляется одним SMS или, если он больше, конкатенированной SMS-PP загрузкой до 5 сегментов, так что большой <code>.cap</code> просто занимает несколько SMS. Сразу после выбора файл проверяется и в форме показывается <strong>оценка требований к памяти</strong>: <strong>требование NVRAM</strong> (образ кода + постоянные данные, сумма в стиле C6+C8 из GP Card Spec) и оценка RAM. Там же показаны <strong>библиотеки, с которыми слинкован CAP</strong> (его компонент Import: имя библиотеки, AID и минимальная версия <code class="font-mono text-sm">≥ x.y</code> — имена стандартных библиотек и семейства подставляются, а где известно, даётся подсказка о версии Java Card), идентификаторы пакета и апплетов и разбивка по компонентам (размеры и доли в порядке load-файла) — всё сгруппировано по разделам <strong>Package</strong>, <strong>Requires</strong>, <strong>Memory</strong> и <strong>Components</strong>. Строка <strong>Platform</strong> показывает минимальный уровень Java Card, который должна предоставить карта (выводится из импорта <code class="font-mono text-sm">javacard.framework</code>; флаг <code class="font-mono text-sm">int</code> в заголовке дополнительно требует поддержки int — Java Card 2.2+): JCRE проверяет каждый импорт по своим пакетам при верификации load-файла (JC VM spec 4.5.2), поэтому карта со старой платформой отклоняет LOAD сразу после завершения компонента <strong>Import</strong> (в строке шага виден remote SW карты) — меньший размер блока LOAD помогает только если блок превышает буфер карты. Повреждённый файл не проходит анализ, и <strong>Execute</strong> остаётся недоступной до успешного анализа. Оценка носит справочный характер и никогда не используется при установке: NVRAM — это образ кода плюс постоянные данные (сумма в стиле C6+C8 по GP Table 11-48; управление памятью карты, округление и запись в реестре не учитываются, а образ статических полей входит в обе части); рекомендуемый <strong>C6</strong> следует за load-файлом, а рекомендуемые квоты — это десятичные значения в байтах (шестнадцатеричное значение в скобках — это кодирование параметров установки C7/C8: C6 — энергонезависимый код, C7 — энергозависимая RAM, C8 — энергонезависимые данные).</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
+1
-1
@@ -320,7 +320,7 @@
|
||||
<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>Query expanded registry details (E3)</strong> checkbox also requests it as an extra pass and merges the expanded-only fields (privileges, ELF/SD associations, module lists). Results appear in an explorer view with per-item <strong>Delete</strong> buttons and the detected RAM command format.</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. 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+): 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) — 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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
+43
-8
@@ -1732,7 +1732,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.35';
|
||||
const SIMPLE_VERSION = '3.6.36';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -8472,11 +8472,21 @@ function capMemHtml(mem) {
|
||||
return esc(a) + (n ? ' <span class="text-gray-400">(' + esc(n) + ')</span>' : '');
|
||||
}).join('<br>'), 'pl-4 font-mono break-all'));
|
||||
}
|
||||
// ── minimum platform: the card must provide every import version (JC VM
|
||||
// spec 4.5.2 - same major, minor >= the export version) and the JCRE
|
||||
// checks that while the load file is verified, so the framework import
|
||||
// names the minimum Java Card level; the header int flag needs int
|
||||
// support (Java Card 2.2+). capmem computes the level
|
||||
// (requires_java_card); the local table is the fallback for an older
|
||||
// server response.
|
||||
const fw = imports.find(im => (im.aid || '').toUpperCase() === 'A0000000620101');
|
||||
const sdk = fw ? JC_FRAMEWORK_SDK[(Number(fw.major) || 0) + '.' + (Number(fw.minor) || 0)] : '';
|
||||
let against = sdk ? 'Java Card ' + sdk : '';
|
||||
const sdk = mem.requires_java_card ||
|
||||
(fw ? JC_FRAMEWORK_SDK[(Number(fw.major) || 0) + '.' + (Number(fw.minor) || 0)] : '');
|
||||
let against = sdk ? 'Java Card \u2265 ' + sdk
|
||||
: (fw ? 'javacard.framework \u2265 ' + (Number(fw.major) || 0) + '.' + (Number(fw.minor) || 0) : '');
|
||||
if (mem.cap_version) against += (against ? ' \u00b7 ' : '') + t('CAP format') + ' ' + mem.cap_version;
|
||||
if (against) pkgRows += row(label(esc(t('Compiled'))) + cell(esc(against), 'pl-4'));
|
||||
if (mem.needs_int) against += (against ? ' \u00b7 ' : '') + t('int support');
|
||||
if (against) pkgRows += row(label(esc(t('Platform'))) + cell(esc(against), 'pl-4'));
|
||||
if (pkgRows) html += section(t('Package')) + table(pkgRows);
|
||||
|
||||
// ── the libraries the CAP is linked against (JC VM spec 6.6), shown as
|
||||
@@ -9375,6 +9385,25 @@ function ramStepLine(s, idx) {
|
||||
return line;
|
||||
}
|
||||
|
||||
// Hint after a failed RAM install (pure). A rejected LOAD / INSTALL [for
|
||||
// load] step has two known causes: the card's platform is older than the
|
||||
// CAP requires (the JCRE checks every import while it verifies the load file,
|
||||
// JC VM spec 4.5.2, and refuses the step as soon as the Import component is
|
||||
// complete - observed remote SW 6438/6985 exactly at that block boundary),
|
||||
// or the LOAD block exceeds the card's buffer. `mem` is the CAP analysis
|
||||
// (capmem memory_json) when the install came from a .cap.
|
||||
function ramInstallFailHint(data, mem) {
|
||||
if (!data || data.success) return '';
|
||||
const steps = data.steps || [];
|
||||
const step = steps[(Number(data.failed_step) || 1) - 1] || null;
|
||||
const name = step ? String(step.name || '') : '';
|
||||
if (!/^(LOAD |INSTALL \[for load\])/.test(name)) return '';
|
||||
let hint = t('a LOAD rejected here means the card refused the CAP: every import version under Requires must be present on the card');
|
||||
if (mem && mem.requires_java_card) hint += ' (Java Card \u2265 ' + mem.requires_java_card + ')';
|
||||
hint += t('. If the CAP needs only versions this card has, retry with a smaller LOAD block (e.g. 64 B)');
|
||||
return hint;
|
||||
}
|
||||
|
||||
async function ramInstallCap(sp) {
|
||||
const fileInput = document.getElementById('ram-cap-file');
|
||||
const file = fileInput.files[0];
|
||||
@@ -9426,7 +9455,7 @@ async function ramInstallCap(sp) {
|
||||
if (data.load_block_size_auto) sizeInfo += ' (' + t('default') + ')';
|
||||
}
|
||||
ramShowInstallResult(data, t('Install OK') + ' — load_file_aid=' + data.load_file_aid
|
||||
+ ' module_aid=' + data.module_aid + sizeInfo);
|
||||
+ ' module_aid=' + data.module_aid + sizeInfo, _capAnalysis.ram.memory);
|
||||
}
|
||||
|
||||
// INSTALL [for install] / [for make selectable] for an already loaded
|
||||
@@ -9474,7 +9503,7 @@ async function ramInstallApp(sp, op) {
|
||||
|
||||
// Shared result rendering for /api/ram-install and /api/ram-install-app: the
|
||||
// per-step lines, the counter the card consumed, and the verdict.
|
||||
function ramShowInstallResult(data, okText) {
|
||||
function ramShowInstallResult(data, okText, capMem) {
|
||||
const resultEl = document.getElementById('ram-result');
|
||||
const stepsEl = document.getElementById('ram-steps');
|
||||
stepsEl.classList.remove('hidden');
|
||||
@@ -9489,7 +9518,9 @@ function ramShowInstallResult(data, okText) {
|
||||
resultEl.textContent = okText || t('OK');
|
||||
resultEl.classList.remove('hidden', 'text-red-600'); resultEl.classList.add('text-green-600');
|
||||
} else {
|
||||
resultEl.textContent = t('Install FAILED at step:') + ' ' + data.failed_step + (data.error ? ' (' + data.error + ')' : '');
|
||||
const hint = ramInstallFailHint(data, capMem || null);
|
||||
resultEl.textContent = t('Install FAILED at step:') + ' ' + data.failed_step + (data.error ? ' (' + data.error + ')' : '') +
|
||||
(hint ? ' \u2014 ' + t('Hint:') + ' ' + hint : '');
|
||||
resultEl.classList.remove('hidden', 'text-green-600'); resultEl.classList.add('text-red-600');
|
||||
}
|
||||
ramOpModalFinish(resultEl.textContent, !!data.success, txt);
|
||||
@@ -17116,7 +17147,8 @@ const LANG_RU = {
|
||||
'Version': 'Версия',
|
||||
'Type': 'Тип',
|
||||
'Applets': 'Апплеты',
|
||||
'Compiled': 'Собрано',
|
||||
'Platform': 'Платформа',
|
||||
'int support': 'поддержка int',
|
||||
'Requires': 'Требуются',
|
||||
'Family': 'Семейство',
|
||||
'AID': 'AID',
|
||||
@@ -17143,6 +17175,9 @@ const LANG_RU = {
|
||||
'Step': 'Шаг',
|
||||
'Install OK': 'Установка OK',
|
||||
'Install FAILED at step:': 'Установка не удалась на шаге:',
|
||||
'Hint:': 'Подсказка:',
|
||||
'a LOAD rejected here means the card refused the CAP: every import version under Requires must be present on the card': 'отклонение LOAD здесь означает, что карта отклонила CAP: все версии импортов из «Требуются» должны присутствовать на карте',
|
||||
'. If the CAP needs only versions this card has, retry with a smaller LOAD block (e.g. 64 B)': '. Если CAP требует только доступные этой карте версии, повторите с меньшим размером блока LOAD (например, 64 Б)',
|
||||
'Profile from snapshot': 'Профиль из снимка',
|
||||
'Profile verification results for:': 'Результаты проверки профиля:',
|
||||
'Snapshot comparison results:': 'Результаты сравнения снимков:',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v308';
|
||||
const CACHE = 'simple-v309';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -119,11 +119,12 @@ test('capMemHtml renders the grouped report: package, requires, memory, componen
|
||||
assert.ok(req.includes('>\u2265 1.0</td>'), req);
|
||||
assert.ok(req.includes('>Oracle JavaCard API</td>'), req);
|
||||
assert.ok(req.includes('>6</td>'), req);
|
||||
// package identity + compiled-against hint derived from the framework version
|
||||
// package identity + platform requirement derived from the framework version
|
||||
assert.ok(out.includes('A0000000620101') && out.includes('>v1.0</td>'), out);
|
||||
assert.ok(out.includes('applet package'), out);
|
||||
assert.ok(out.includes('(javacard.framework)</span>'), out);
|
||||
assert.ok(out.includes('Java Card 2.1.1/2.1.2 \u00b7 CAP format 2.1'), out);
|
||||
assert.ok(out.includes('>Platform</td>'), out);
|
||||
assert.ok(out.includes('Java Card \u2265 2.1.1/2.1.2 \u00b7 CAP format 2.1'), out);
|
||||
// components in load-file order with sizes, shares and the total row
|
||||
assert.ok(out.includes('>Header</td>') && out.includes('>20 B</td>') && out.includes('>1%</td>'), out);
|
||||
assert.ok(out.includes('>Method</td>') && out.includes('>2.7 kB</td>') && out.includes('>93%</td>'), out);
|
||||
@@ -134,6 +135,25 @@ test('capMemHtml renders the grouped report: package, requires, memory, componen
|
||||
assert.strictEqual(capMemHtml(null), '');
|
||||
});
|
||||
|
||||
test('capMemHtml shows the server platform requirement and int support', () => {
|
||||
const mem = memFixture();
|
||||
mem.requires_java_card = '2.2.2';
|
||||
mem.requires_framework = '1.3';
|
||||
mem.needs_int = true;
|
||||
const out = capMemHtml(mem);
|
||||
assert.ok(out.includes('Java Card \u2265 2.2.2 \u00b7 CAP format 2.1 \u00b7 int support'), out);
|
||||
// the server value wins over the local fallback table (fixture import 1.0)
|
||||
assert.ok(!out.includes('2.1.1/2.1.2'), out);
|
||||
});
|
||||
|
||||
test('capMemHtml falls back to the raw framework version outside the corpus', () => {
|
||||
const mem = memFixture();
|
||||
mem.imports = [{ aid: 'A0000000620101', minor: 0, major: 7, refs: 6 }];
|
||||
const out = capMemHtml(mem);
|
||||
assert.ok(out.includes('javacard.framework \u2265 7.0 \u00b7 CAP format 2.1'), out);
|
||||
assert.ok(!out.includes('Java Card'), out);
|
||||
});
|
||||
|
||||
test('capMemHtml skips the import section on a response without imports', () => {
|
||||
const mem = memFixture();
|
||||
delete mem.imports;
|
||||
|
||||
@@ -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', 'ramGetStatusApdu', 'ramDeleteApdu',
|
||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', '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,26 @@ test('ramStepLine shows the PoR verdict and the remote status word', () => {
|
||||
assert.ok(noPor.startsWith('\u2705') && noPor.includes('no PoR'), noPor);
|
||||
});
|
||||
|
||||
test('ramInstallFailHint names the CAP import requirement for a rejected LOAD', () => {
|
||||
globalThis.t = s => s;
|
||||
const failedLoad = {
|
||||
success: false, failed_step: 3,
|
||||
steps: [{ name: 'INSTALL [for load]' }, { name: 'LOAD (1/240)' }, { name: 'LOAD (2/240)', por_sw: '6985' }],
|
||||
};
|
||||
const hint = ramInstallFailHint(failedLoad, { requires_java_card: '2.2.2' });
|
||||
assert.ok(hint.includes('every import version under Requires'), hint);
|
||||
assert.ok(hint.includes('(Java Card \u2265 2.2.2)'), hint);
|
||||
assert.ok(hint.includes('smaller LOAD block'), hint);
|
||||
// without a CAP analysis (INSTALL [for install] ops) the level is omitted
|
||||
const noMem = ramInstallFailHint(failedLoad, null);
|
||||
assert.ok(noMem.includes('every import version under Requires'), noMem);
|
||||
assert.ok(!noMem.includes('Java Card'), noMem);
|
||||
// only a load-related step gets the hint
|
||||
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), '');
|
||||
});
|
||||
|
||||
test('ramOpChanged clears the executed status only on a real op change', () => {
|
||||
const els = fakeRamDocument(['ram-op', 'ram-install-params', 'ram-result', 'ram-explorer', 'ram-steps', 'ram-progress']);
|
||||
_ramOpLast = null;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.6.35"
|
||||
version = "3.6.36"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
requires-python = ">=3.8"
|
||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||
|
||||
@@ -1444,6 +1444,21 @@ def _external_ref_counts(cap):
|
||||
return counts
|
||||
|
||||
|
||||
# javacard.framework export version -> Java Card release family whose kit
|
||||
# ships exactly that version. Derived from the local Oracle SDK kit corpus
|
||||
# (jc211_kit .. jc305u4_kit exports, 2026-09-27; same table as the PWA's
|
||||
# JC_FRAMEWORK_SDK). A card provides the framework export of its platform,
|
||||
# and JC VM spec 4.5.2 makes the CAP's import version a ">=" demand on it
|
||||
# (same major, minor >= the export version), checked by the JCRE while the
|
||||
# load file is verified - so the framework import names the minimum Java Card
|
||||
# level a CAP can load on.
|
||||
JC_FRAMEWORK_AID = 'A0000000620101'
|
||||
JC_FRAMEWORK_JDK = {
|
||||
'1.0': '2.1.1/2.1.2', '1.2': '2.2.1', '1.3': '2.2.2',
|
||||
'1.4': '3.0.3', '1.5': '3.0.4', '1.6': '3.0.5',
|
||||
}
|
||||
|
||||
|
||||
def analyze_bytes(cap_bytes, verbose=False):
|
||||
"""Analyze a CAP archive (bytes) and return (report, memory)."""
|
||||
cap = CAP(cap_bytes)
|
||||
@@ -1472,6 +1487,21 @@ def analyze_bytes(cap_bytes, verbose=False):
|
||||
h = cap.components['header']
|
||||
report['flags'] = h.flags
|
||||
report['package_name'] = h.package_name
|
||||
# flags bit 0 ('int', set by the JC 2.1.2 converter's -i): the loader
|
||||
# must support int (Java Card 2.2+)
|
||||
report['needs_int'] = bool(h.flags & 0x01)
|
||||
|
||||
# Minimum platform (JC VM spec 4.5.2): the card must export every
|
||||
# imported package at >= the version recorded in the CAP, and the JCRE
|
||||
# performs that check while the load file is verified. The framework
|
||||
# import names the minimum Java Card level (a card with an older platform
|
||||
# rejects the LOAD as soon as the Import component is complete).
|
||||
if cap.has('import'):
|
||||
fw = next((p for p in cap.components['import'].packages
|
||||
if p.aid_hex == JC_FRAMEWORK_AID), None)
|
||||
if fw:
|
||||
report['requires_framework'] = f'{fw.major}.{fw.minor}'
|
||||
report['requires_java_card'] = JC_FRAMEWORK_JDK.get(report['requires_framework'])
|
||||
|
||||
# Component sizes (the load file order) for the breakdown display
|
||||
report['components'] = [{'name': name, 'size': size} for name, size in cap.files]
|
||||
@@ -1710,6 +1740,12 @@ def memory_json(report, memory, load_file_bytes=None):
|
||||
'applet': bool(report.get('flags', 0) & 0x04),
|
||||
},
|
||||
'package_name': report.get('package_name'),
|
||||
# minimum platform the card must provide for this load file (JC VM
|
||||
# spec 4.5.2, derived from the imports): the Java Card level named by
|
||||
# the framework import, and the header int flag's loader requirement
|
||||
'requires_framework': report.get('requires_framework'),
|
||||
'requires_java_card': report.get('requires_java_card'),
|
||||
'needs_int': bool(report.get('needs_int')),
|
||||
'components': report.get('components', []),
|
||||
'class_count': report.get('class_count', 0),
|
||||
'method_count': report.get('method_count', 0),
|
||||
@@ -1756,6 +1792,14 @@ def format_report(report, memory):
|
||||
|
||||
if 'cap_version' in report:
|
||||
lines.append(f'CAP format: {report["cap_version"]}')
|
||||
if report.get('requires_java_card') or report.get('requires_framework'):
|
||||
req = 'Requires Java Card >= %s' % (report.get('requires_java_card')
|
||||
or report.get('requires_framework'))
|
||||
if report.get('requires_java_card') and report.get('requires_framework'):
|
||||
req += ' (javacard.framework %s)' % report['requires_framework']
|
||||
if report.get('needs_int'):
|
||||
req += ', int support'
|
||||
lines.append(req)
|
||||
if 'package_version' in report:
|
||||
lines.append(f'Package version: {report["package_version"]}')
|
||||
if 'package_aid' in report:
|
||||
|
||||
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.6.35'
|
||||
VERSION = '3.6.36'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Tests for the bundled CAP memory analyzer (capmem) and /api/cap-info helper."""
|
||||
|
||||
import io
|
||||
import pathlib
|
||||
import re
|
||||
import struct
|
||||
import unittest
|
||||
import zipfile
|
||||
@@ -228,6 +230,38 @@ class TestCapAnalyzer(unittest.TestCase):
|
||||
report, _ = capmem.analyze_bytes(build_cap())
|
||||
self.assertIsNone(report['package_name'])
|
||||
|
||||
def test_framework_sdk_table_matches_the_pwa(self):
|
||||
# capmem.JC_FRAMEWORK_JDK mirrors the PWA's fallback JC_FRAMEWORK_SDK
|
||||
# (drift guard: the PWA shows the server value when it is present)
|
||||
html = pathlib.Path(__file__).resolve().parents[1].joinpath(
|
||||
'frontend', 'index.html').read_text(encoding='utf-8')
|
||||
m = re.search(r"const JC_FRAMEWORK_SDK = \{(.*?)\};", html, re.S)
|
||||
self.assertIsNotNone(m, 'JC_FRAMEWORK_SDK not found in index.html')
|
||||
pwa = dict(re.findall(r"'([\d.]+)':\s*'([^']+)'", m.group(1)))
|
||||
self.assertEqual(pwa, capmem.JC_FRAMEWORK_JDK)
|
||||
|
||||
def test_platform_requirement_from_the_framework_import(self):
|
||||
# java.lang only: no Java Card level named, but the int flag shows
|
||||
report, _ = capmem.analyze_bytes(
|
||||
build_cap(imports=[(0, 1, bytes.fromhex('A0000000620001'))], header_flags=0x01))
|
||||
info = capmem.memory_json(report, capmem.compute_memory(report))
|
||||
self.assertIsNone(info['requires_framework'])
|
||||
self.assertIsNone(info['requires_java_card'])
|
||||
self.assertTrue(info['needs_int'])
|
||||
# javacard.framework 1.3 -> Java Card 2.2.2 (the kit corpus mapping)
|
||||
report, _ = capmem.analyze_bytes(
|
||||
build_cap(imports=[(3, 1, JAVACARD_FRAMEWORK)], header_flags=0x05))
|
||||
info = capmem.memory_json(report, capmem.compute_memory(report))
|
||||
self.assertEqual(info['requires_framework'], '1.3')
|
||||
self.assertEqual(info['requires_java_card'], '2.2.2')
|
||||
self.assertTrue(info['needs_int'])
|
||||
# a version outside the corpus keeps the raw framework version
|
||||
report, _ = capmem.analyze_bytes(
|
||||
build_cap(imports=[(0, 2, JAVACARD_FRAMEWORK)]))
|
||||
info = capmem.memory_json(report, capmem.compute_memory(report))
|
||||
self.assertEqual(info['requires_framework'], '2.0')
|
||||
self.assertIsNone(info['requires_java_card'])
|
||||
|
||||
def test_memory_json_nvram_requirement_uses_the_load_file(self):
|
||||
report, memory = capmem.analyze_bytes(rich_cap())
|
||||
info = capmem.memory_json(report, memory, load_file_bytes=1000)
|
||||
|
||||
Reference in New Issue
Block a user