feat: approximate NVRAM requirement from the CAP analysis (v3.5.15)
GlobalPlatform models this explicitly (GP Card Spec v2.3.1 Table 11-48, load parameters): C6 = non-volatile code, C7 = volatile data, C8 = non-volatile data, and 11.5.2.3.7 - when the card makes no code/data distinction the required minimum is C6 + C8. The analysis now reports it: - capmem.memory_json() accepts the load file size (all CAP components - the package image the card stores) and returns code.load_file, nvram.requirement = load_file + persistent data, and sets the suggested C6 to the load file (the bytecode-only Method.cap figure stays in code.method_component); _cap_info_body passes the size it already computed. - The CAP estimate box (both the RAM installer and the SCP81 Install-from-.cap form) leads with "NVRAM requirement ≈ code image + data" plus the RAM estimate, keeps the bytecode/other-component split and the full data breakdown in Details, and carries the GP citation with the caveats (card memory management, allocation rounding and the registry entry are not included; the static field image appears in both parts). - An older server response without code.load_file still renders (the bytecode size is used as the fallback). Tests: Python +1 (load-file semantics) with extended cap-info assertions, frontend +1 (renderer + fallback). Help EN/RU, docs/api.md, AGENTS. 587 frontend / 472 Python green; version 3.5.15; sw simple-v268.
This commit is contained in:
@@ -319,7 +319,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 для информации о памяти. Результаты отображаются в обзоре с кнопками <strong>Удалить</strong> для каждого элемента.</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> (код / NVRAM / RAM); повреждённый файл не проходит анализ, и <strong>Execute</strong> остаётся недоступной до успешного анализа.</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>Execute</strong> остаётся недоступной до успешного анализа.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -376,7 +376,7 @@
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Empty</strong> — начать с пустого списка.</li>
|
||||
<li><strong>Explore ISD</strong> — эталонная административная последовательность (<code class="font-mono text-sm">GET DATA FF21</code>, листинги GET STATUS ISD/ELF/приложений, <code class="font-mono text-sm">GET DATA 0085</code>); длинные листинги автоматически продолжаются страницами <code class="font-mono text-sm">SW 6310/CAFE</code>.</li>
|
||||
<li><strong>Install from .cap</strong> — выберите <code class="font-mono text-sm">.cap</code>, при необходимости SD AID, параметры install/STK и <em>Make selectable</em>; кнопка <strong>Сгенерировать</strong> строит INSTALL [for load] → LOAD ×N → INSTALL [for install]. Файл используется только для генерации APDU — он не сохраняется, как и его имя. При выборе файл проверяется и показывается оценка требований к памяти (код / NVRAM / RAM); <strong>Сгенерировать</strong> недоступна до успешного анализа.</li>
|
||||
<li><strong>Install from .cap</strong> — выберите <code class="font-mono text-sm">.cap</code>, при необходимости SD AID, параметры install/STK и <em>Make selectable</em>; кнопка <strong>Сгенерировать</strong> строит INSTALL [for load] → LOAD ×N → INSTALL [for install]. Файл используется только для генерации APDU — он не сохраняется, как и его имя. При выборе файл проверяется и показывается оценка требований к памяти (требование NVRAM как сумма образа кода и постоянных данных, плюс RAM); <strong>Сгенерировать</strong> недоступна до успешного анализа.</li>
|
||||
<li><strong>Delete AID</strong> — список AID (по одному в строке) и P2 (<em>object only</em> / <em>object and related objects</em>) → APDU DELETE.</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">Таблица показывает имя, тип, число APDU и время создания каждого скрипта, а также кнопки <strong>Редактировать</strong> и <strong>Удалить</strong>; в редакторе есть поле имени и текстовая область APDU. В ходе сессии сервер отдаёт по одному C-APDU на каждый POST карты и отслеживает выполнение: карта сообщает статус в следующем POST (<code class="font-mono text-sm">X-Admin-Script-Status</code>), оборвавшаяся сессия досылает только невыполненные APDU (<code class="font-mono text-sm">X-Admin-Resume</code> продолжает, новый диалог начинает заново), а завершённый скрипт закрывается ответом <code class="font-mono text-sm">204 No Content</code>. Конструктор RAM/GP вкладки Remote APDU может отправить цепочку команд прямо в прогон кнопкой <strong>«В очередь SCP81»</strong>.</p>
|
||||
|
||||
+2
-2
@@ -318,7 +318,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 for memory info. Results appear in an explorer view with per-item <strong>Delete</strong> buttons.</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> (code / NVRAM / RAM) in the form; a corrupt or wrong-format file fails the analysis, and <strong>Execute</strong> stays disabled until a successful analysis.</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; a corrupt or wrong-format file fails the analysis, and <strong>Execute</strong> stays disabled until a successful analysis.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -375,7 +375,7 @@
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Empty</strong> — start from scratch.</li>
|
||||
<li><strong>Explore ISD</strong> — the reference administration sequence (<code class="font-mono text-sm">GET DATA FF21</code>, GET STATUS ISD/ELF/application listings, <code class="font-mono text-sm">GET DATA 0085</code>); long listings auto-continue through the <code class="font-mono text-sm">SW 6310/CAFE</code> pages.</li>
|
||||
<li><strong>Install from .cap</strong> — pick a <code class="font-mono text-sm">.cap</code> plus optional SD AID, install/STK parameters and <em>make selectable</em>; <strong>Generate</strong> builds INSTALL [for load] → LOAD ×N → INSTALL [for install]. The file is only used to generate the APDUs — it is not stored, not even its name. On selection it is validated and its memory requirements are estimated (code / NVRAM / RAM); <strong>Generate</strong> stays disabled until the analysis succeeds.</li>
|
||||
<li><strong>Install from .cap</strong> — pick a <code class="font-mono text-sm">.cap</code> plus optional SD AID, install/STK parameters and <em>make selectable</em>; <strong>Generate</strong> builds INSTALL [for load] → LOAD ×N → INSTALL [for install]. The file is only used to generate the APDUs — it is not stored, not even its name. On selection it is validated and its memory requirements are estimated (the NVRAM requirement as the code-image + persistent-data total, plus RAM); <strong>Generate</strong> stays disabled until the analysis succeeds.</li>
|
||||
<li><strong>Delete AID</strong> — AID list (one per line) and P2 (<em>object only</em> / <em>object and related objects</em>) → DELETE APDUs.</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">The table lists each script with its kind, APDU count and creation time, plus <strong>Edit</strong> and <strong>Delete</strong>; the editor has a name field and the APDU textarea. Over a session the server serves one C-APDU per card POST and tracks execution: the card reports status in its next POST (<code class="font-mono text-sm">X-Admin-Script-Status</code>), a session that dies resends only the unexecuted APDUs (<code class="font-mono text-sm">X-Admin-Resume</code> continues, a fresh dialog restarts), and a completed script is closed with <code class="font-mono text-sm">204 No Content</code>. The Remote APDU tab's RAM/GP builder can feed a command chain straight into the run with <strong>Queue in SCP81</strong>.</p>
|
||||
|
||||
+28
-5
@@ -1616,7 +1616,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.5.14';
|
||||
const SIMPLE_VERSION = '3.5.15';
|
||||
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||
|
||||
// ===== Tab switching =====
|
||||
@@ -8025,21 +8025,36 @@ function capMemHtml(mem) {
|
||||
if (!mem) return '';
|
||||
const nv = mem.nvram || {}, ram = mem.ram || {}, code = mem.code || {}, sug = mem.suggested || {};
|
||||
const hex2 = v => (Number(v) || 0).toString(16).toUpperCase().padStart(4, '0');
|
||||
const methodBytes = Number(code.method_component) || 0;
|
||||
// older server responses carry only the bytecode size: fall back to it
|
||||
const loadFile = Number(code.load_file) || methodBytes;
|
||||
const nvramData = Number(nv.total) || 0;
|
||||
const requirement = Number(nv.requirement) || (loadFile + nvramData);
|
||||
const otherComponents = Math.max(0, loadFile - methodBytes);
|
||||
let html = '<div class="font-medium text-gray-700 dark:text-slate-300">' + esc(t('CAP requirements (estimate)')) + '</div>';
|
||||
html += '<div>' + esc(t('Code (Method.cap):')) + ' ' + esc(capMemBytes(code.method_component)) +
|
||||
' · ' + esc(t('Persistent (NVRAM):')) + ' ' + esc(capMemBytes(nv.total)) +
|
||||
// the NVRAM requirement is a C6+C8-style total (GP Card Spec v2.3.1
|
||||
// Table 11-48): the package image (load file) plus the persistent data
|
||||
html += '<div>' + esc(t('NVRAM requirement ≈')) + ' ' + esc(capMemBytes(requirement)) +
|
||||
' (' + esc(t('code image')) + ' ' + esc(capMemBytes(loadFile)) +
|
||||
' + ' + esc(t('data')) + ' ' + esc(capMemBytes(nvramData)) + ')' +
|
||||
' · ' + esc(t('RAM (volatile):')) + ' ' + esc(capMemBytes(ram.total)) + '</div>';
|
||||
html += '<details class="mt-1"><summary class="cursor-pointer text-gray-400">' + esc(t('Details')) + '</summary>';
|
||||
html += '<div>' + esc(t('Static image:')) + ' ' + esc(capMemBytes(nv.static_image)) +
|
||||
html += '<div>' + esc(t('Code image (load file):')) + ' ' + esc(capMemBytes(loadFile)) +
|
||||
' · ' + esc(t('bytecode (Method.cap):')) + ' ' + esc(capMemBytes(methodBytes)) +
|
||||
' · ' + esc(t('other components:')) + ' ' + esc(capMemBytes(otherComponents)) + '</div>';
|
||||
html += '<div>' + esc(t('Persistent data (NVRAM):')) + ' ' + esc(capMemBytes(nvramData)) +
|
||||
' · ' + esc(t('Static image:')) + ' ' + esc(capMemBytes(nv.static_image)) +
|
||||
' · ' + esc(t('Static array init:')) + ' ' + esc(capMemBytes(nv.array_init)) +
|
||||
' · ' + esc(t('Install-time objects:')) + ' ' + esc(capMemBytes(nv.install_objects)) +
|
||||
' · ' + esc(t('Object headers:')) + ' ' + esc(capMemBytes(nv.header_overhead)) +
|
||||
' · ' + esc(t('Reference storage:')) + ' ' + esc(capMemBytes(nv.ref_storage)) + '</div>';
|
||||
html += '<div>' + esc(t('Transient arrays:')) + ' ' + esc(capMemBytes(ram.transient_arrays)) +
|
||||
html += '<div>' + esc(t('RAM (volatile):')) + ' ' + esc(capMemBytes(ram.total)) +
|
||||
' · ' + esc(t('Transient arrays:')) + ' ' + esc(capMemBytes(ram.transient_arrays)) +
|
||||
' · ' + esc(t('Runtime transient:')) + ' ' + esc(capMemBytes(ram.runtime_transient)) +
|
||||
' · ' + esc(t('Peak method frame:')) + ' ' + esc(capMemBytes(ram.peak_frame)) + '</div>';
|
||||
html += '<div>' + esc(t('Suggested install quotas (informational):')) +
|
||||
' C6=' + esc(String(Number(sug.c6) || 0)) + ' C7=0x' + esc(hex2(sug.c7)) + ' C8=0x' + esc(hex2(sug.c8)) + '</div>';
|
||||
html += '<div class="text-gray-400">' + esc(t('NVRAM = code image + persistent data (GP Card Spec v2.3.1 Table 11-48, C6+C8 style); card memory management, rounding and the registry entry are not included, and the static field image appears in both parts.')) + '</div>';
|
||||
html += '<div class="text-gray-400">' + esc(t('Estimate only — memory values are not used for installation')) + '</div>';
|
||||
html += '</details>';
|
||||
(mem.warnings || []).forEach(w => {
|
||||
@@ -16012,6 +16027,14 @@ const LANG_RU = {
|
||||
'Retry': 'Повторить',
|
||||
'Analyze the CAP file first': 'Сначала проанализируйте CAP-файл',
|
||||
'CAP requirements (estimate)': 'Требования CAP (оценка)',
|
||||
'NVRAM requirement ≈': 'Требование NVRAM ≈',
|
||||
'code image': 'образ кода',
|
||||
'data': 'данные',
|
||||
'Code image (load file):': 'Образ кода (load-файл):',
|
||||
'bytecode (Method.cap):': 'байткод (Method.cap):',
|
||||
'other components:': 'прочие компоненты:',
|
||||
'Persistent data (NVRAM):': 'Постоянные данные (NVRAM):',
|
||||
'NVRAM = code image + persistent data (GP Card Spec v2.3.1 Table 11-48, C6+C8 style); card memory management, rounding and the registry entry are not included, and the static field image appears in both parts.': 'NVRAM = образ кода + постоянные данные (GP Card Spec v2.3.1 Table 11-48, стиль C6+C8); управление памятью карты, округление и запись в реестре не учтены, а образ статических полей входит в обе части.',
|
||||
'Code (Method.cap):': 'Код (Method.cap):',
|
||||
'Persistent (NVRAM):': 'Энергонезависимая память (NVRAM):',
|
||||
'RAM (volatile):': 'ОЗУ (volatile):',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v267';
|
||||
const CACHE = 'simple-v268';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -48,10 +48,10 @@ function resetState() {
|
||||
|
||||
function memFixture() {
|
||||
return {
|
||||
code: { method_component: 2048 },
|
||||
nvram: { static_image: 12, array_init: 4, install_objects: 100, header_overhead: 12, ref_storage: 8, total: 136, runtime: 0 },
|
||||
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 },
|
||||
suggested: { c6: 2048, c7: 272, c8: 136 },
|
||||
suggested: { c6: 3000, c7: 272, c8: 136 },
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
@@ -64,23 +64,36 @@ test('capMemBytes formats bytes and kilobytes', () => {
|
||||
assert.strictEqual(capMemBytes(null), '0 B');
|
||||
});
|
||||
|
||||
test('capMemHtml renders the summary, breakdown and warnings', () => {
|
||||
test('capMemHtml renders the NVRAM requirement, breakdown and warnings', () => {
|
||||
const mem = memFixture();
|
||||
mem.warnings = ['method class[0].token[1]: unknown opcode 0xAA, scan stopped'];
|
||||
const out = capMemHtml(mem);
|
||||
assert.ok(out.includes('CAP requirements (estimate)'), out);
|
||||
assert.ok(out.includes('Code (Method.cap): 2.0 kB'), out);
|
||||
assert.ok(out.includes('Persistent (NVRAM): 136 B'), out);
|
||||
// C6+C8-style total: load file + persistent data
|
||||
assert.ok(out.includes('NVRAM requirement ≈ 3.1 kB (code image 2.9 kB + data 136 B)'), out);
|
||||
assert.ok(out.includes('RAM (volatile): 24 B'), out);
|
||||
assert.ok(out.includes('Code image (load file): 2.9 kB'), out);
|
||||
assert.ok(out.includes('bytecode (Method.cap): 2.0 kB'), out);
|
||||
assert.ok(out.includes('other components: 952 B'), out);
|
||||
assert.ok(out.includes('Persistent data (NVRAM): 136 B'), out);
|
||||
assert.ok(out.includes('Static image: 12 B'), out);
|
||||
assert.ok(out.includes('Reference storage: 8 B'), out);
|
||||
assert.ok(out.includes('Peak method frame: 8 B'), out);
|
||||
assert.ok(out.includes('C6=2048 C7=0x0110 C8=0x0088'), out);
|
||||
assert.ok(out.includes('C6=3000 C7=0x0110 C8=0x0088'), out);
|
||||
assert.ok(out.includes('Table 11-48'), out);
|
||||
assert.ok(out.includes('Estimate only'), out);
|
||||
assert.ok(out.includes('unknown opcode 0xAA'), out);
|
||||
assert.strictEqual(capMemHtml(null), '');
|
||||
});
|
||||
|
||||
test('capMemHtml falls back to the bytecode size on older server responses', () => {
|
||||
const mem = memFixture();
|
||||
delete mem.code.load_file;
|
||||
delete mem.nvram.requirement;
|
||||
const out = capMemHtml(mem);
|
||||
assert.ok(out.includes('NVRAM requirement ≈ 2.1 kB (code image 2.0 kB + data 136 B)'), out);
|
||||
});
|
||||
|
||||
test('capGateOk gates the RAM install op and the scripts form', () => {
|
||||
resetState();
|
||||
globalThis.document = { getElementById: id => (id === 'ram-op' ? { value: 'install-cap' } : null) };
|
||||
|
||||
Reference in New Issue
Block a user