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:
+13
-2
@@ -349,9 +349,10 @@ used for installation.
|
|||||||
"package_aid": "AA1902BC226001",
|
"package_aid": "AA1902BC226001",
|
||||||
"applet_count": 1, "applets": ["AA1902BC226001"],
|
"applet_count": 1, "applets": ["AA1902BC226001"],
|
||||||
"class_count": 3, "method_count": 12,
|
"class_count": 3, "method_count": 12,
|
||||||
"code": {"method_component": 850},
|
"code": {"method_component": 850, "load_file": 1234},
|
||||||
"nvram": {"static_image": 12, "array_init": 4, "install_objects": 100,
|
"nvram": {"static_image": 12, "array_init": 4, "install_objects": 100,
|
||||||
"header_overhead": 24, "ref_storage": 8, "total": 148, "runtime": 0},
|
"header_overhead": 24, "ref_storage": 8, "total": 148, "runtime": 0,
|
||||||
|
"requirement": 1382},
|
||||||
"ram": {"transient_arrays": 16, "runtime_transient": 0, "peak_frame": 8, "total": 24},
|
"ram": {"transient_arrays": 16, "runtime_transient": 0, "peak_frame": 8, "total": 24},
|
||||||
"suggested": {"c6": 850, "c7": 272, "c8": 148},
|
"suggested": {"c6": 850, "c7": 272, "c8": 148},
|
||||||
"warnings": []
|
"warnings": []
|
||||||
@@ -359,6 +360,16 @@ used for installation.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`code.load_file` is the concatenated load file (all components) - the
|
||||||
|
package image the card stores, our proxy for the GlobalPlatform Card Spec
|
||||||
|
v2.3.1 Table 11-48 "non-volatile code" minimum memory requirement; the
|
||||||
|
tool's bytecode-only figure stays in `code.method_component`. The suggested
|
||||||
|
`C6` follows the load file, and `nvram.requirement` = `code.load_file` +
|
||||||
|
`nvram.total` (the C6+C8-style total per §11.5.2.3.7: with no code/data
|
||||||
|
split in the card's memory the required minimum is the sum of both). The
|
||||||
|
estimate excludes card-specific memory management, allocation rounding and
|
||||||
|
the GP registry entry.
|
||||||
|
|
||||||
**Errors** (HTTP 200 with `ok: false`, like `/api/scp81/gen-install`):
|
**Errors** (HTTP 200 with `ok: false`, like `/api/scp81/gen-install`):
|
||||||
`{"ok": false, "error": "cap parse failed: File is not a zip file"}` for a
|
`{"ok": false, "error": "cap parse failed: File is not a zip file"}` for a
|
||||||
corrupt/wrong archive, or `cap analysis failed: …` when a component passes
|
corrupt/wrong archive, or `cap analysis failed: …` when a component passes
|
||||||
|
|||||||
@@ -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>
|
<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>
|
<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 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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@
|
|||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>Empty</strong> — начать с пустого списка.</li>
|
<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>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>
|
<li><strong>Delete AID</strong> — список AID (по одному в строке) и P2 (<em>object only</em> / <em>object and related objects</em>) → APDU DELETE.</li>
|
||||||
</ul>
|
</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>
|
<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>
|
<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>
|
<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 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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@
|
|||||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||||
<li><strong>Empty</strong> — start from scratch.</li>
|
<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>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>
|
<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>
|
</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>
|
<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 =====
|
// ===== Version =====
|
||||||
// Single source of truth for the PWA version: shown in the header and used
|
// Single source of truth for the PWA version: shown in the header and used
|
||||||
// by the server version check in pysimConnect().
|
// 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;
|
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
|
||||||
|
|
||||||
// ===== Tab switching =====
|
// ===== Tab switching =====
|
||||||
@@ -8025,21 +8025,36 @@ function capMemHtml(mem) {
|
|||||||
if (!mem) return '';
|
if (!mem) return '';
|
||||||
const nv = mem.nvram || {}, ram = mem.ram || {}, code = mem.code || {}, sug = mem.suggested || {};
|
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 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>';
|
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)) +
|
// the NVRAM requirement is a C6+C8-style total (GP Card Spec v2.3.1
|
||||||
' · ' + esc(t('Persistent (NVRAM):')) + ' ' + esc(capMemBytes(nv.total)) +
|
// 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>';
|
' · ' + 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 += '<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('Static array init:')) + ' ' + esc(capMemBytes(nv.array_init)) +
|
||||||
' · ' + esc(t('Install-time objects:')) + ' ' + esc(capMemBytes(nv.install_objects)) +
|
' · ' + esc(t('Install-time objects:')) + ' ' + esc(capMemBytes(nv.install_objects)) +
|
||||||
' · ' + esc(t('Object headers:')) + ' ' + esc(capMemBytes(nv.header_overhead)) +
|
' · ' + esc(t('Object headers:')) + ' ' + esc(capMemBytes(nv.header_overhead)) +
|
||||||
' · ' + esc(t('Reference storage:')) + ' ' + esc(capMemBytes(nv.ref_storage)) + '</div>';
|
' · ' + 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('Runtime transient:')) + ' ' + esc(capMemBytes(ram.runtime_transient)) +
|
||||||
' · ' + esc(t('Peak method frame:')) + ' ' + esc(capMemBytes(ram.peak_frame)) + '</div>';
|
' · ' + esc(t('Peak method frame:')) + ' ' + esc(capMemBytes(ram.peak_frame)) + '</div>';
|
||||||
html += '<div>' + esc(t('Suggested install quotas (informational):')) +
|
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>';
|
' 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 += '<div class="text-gray-400">' + esc(t('Estimate only — memory values are not used for installation')) + '</div>';
|
||||||
html += '</details>';
|
html += '</details>';
|
||||||
(mem.warnings || []).forEach(w => {
|
(mem.warnings || []).forEach(w => {
|
||||||
@@ -16012,6 +16027,14 @@ const LANG_RU = {
|
|||||||
'Retry': 'Повторить',
|
'Retry': 'Повторить',
|
||||||
'Analyze the CAP file first': 'Сначала проанализируйте CAP-файл',
|
'Analyze the CAP file first': 'Сначала проанализируйте CAP-файл',
|
||||||
'CAP requirements (estimate)': 'Требования 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):',
|
'Code (Method.cap):': 'Код (Method.cap):',
|
||||||
'Persistent (NVRAM):': 'Энергонезависимая память (NVRAM):',
|
'Persistent (NVRAM):': 'Энергонезависимая память (NVRAM):',
|
||||||
'RAM (volatile):': 'ОЗУ (volatile):',
|
'RAM (volatile):': 'ОЗУ (volatile):',
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v267';
|
const CACHE = 'simple-v268';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ function resetState() {
|
|||||||
|
|
||||||
function memFixture() {
|
function memFixture() {
|
||||||
return {
|
return {
|
||||||
code: { method_component: 2048 },
|
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 },
|
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 },
|
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: [],
|
warnings: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -64,23 +64,36 @@ test('capMemBytes formats bytes and kilobytes', () => {
|
|||||||
assert.strictEqual(capMemBytes(null), '0 B');
|
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();
|
const mem = memFixture();
|
||||||
mem.warnings = ['method class[0].token[1]: unknown opcode 0xAA, scan stopped'];
|
mem.warnings = ['method class[0].token[1]: unknown opcode 0xAA, scan stopped'];
|
||||||
const out = capMemHtml(mem);
|
const out = capMemHtml(mem);
|
||||||
assert.ok(out.includes('CAP requirements (estimate)'), out);
|
assert.ok(out.includes('CAP requirements (estimate)'), out);
|
||||||
assert.ok(out.includes('Code (Method.cap): 2.0 kB'), out);
|
// C6+C8-style total: load file + persistent data
|
||||||
assert.ok(out.includes('Persistent (NVRAM): 136 B'), out);
|
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('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('Static image: 12 B'), out);
|
||||||
assert.ok(out.includes('Reference storage: 8 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('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('Estimate only'), out);
|
||||||
assert.ok(out.includes('unknown opcode 0xAA'), out);
|
assert.ok(out.includes('unknown opcode 0xAA'), out);
|
||||||
assert.strictEqual(capMemHtml(null), '');
|
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', () => {
|
test('capGateOk gates the RAM install op and the scripts form', () => {
|
||||||
resetState();
|
resetState();
|
||||||
globalThis.document = { getElementById: id => (id === 'ram-op' ? { value: 'install-cap' } : null) };
|
globalThis.document = { getElementById: id => (id === 'ram-op' ? { value: 'install-cap' } : null) };
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-simple-server"
|
name = "pysim-simple-server"
|
||||||
version = "3.5.14"
|
version = "3.5.15"
|
||||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -1629,11 +1629,24 @@ def compute_memory(report):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def memory_json(report, memory):
|
def memory_json(report, memory, load_file_bytes=None):
|
||||||
"""Compact, stable JSON shape for the API/PWA (all byte counts)."""
|
"""Compact, stable JSON shape for the API/PWA (all byte counts).
|
||||||
|
|
||||||
|
``load_file_bytes`` is the concatenated CAP load file (all components) -
|
||||||
|
the package image the card stores, our proxy for the GP Card Spec v2.3.1
|
||||||
|
Table 11-48 "non-volatile code" minimum memory requirement. With it the
|
||||||
|
suggested C6 becomes the load file and ``nvram.requirement`` reports the
|
||||||
|
C6+C8-style total (code image + persistent data, per 11.5.2.3.7: "If both
|
||||||
|
tags 'C6' and 'C8' are present and the implementation does not make any
|
||||||
|
distinction between Non-Volatile Code and Non-Volatile Data Memory then
|
||||||
|
the required minimum shall be the sum of both values"); without it the
|
||||||
|
tool's bytecode-only C6 is kept."""
|
||||||
ram_transient = memory.get('ram_transient_arrays', 0)
|
ram_transient = memory.get('ram_transient_arrays', 0)
|
||||||
ram_runtime = memory.get('runtime_transient_bytes', 0)
|
ram_runtime = memory.get('runtime_transient_bytes', 0)
|
||||||
ram_frame = memory.get('ram_frame_bytes', 0)
|
ram_frame = memory.get('ram_frame_bytes', 0)
|
||||||
|
nvram_total = memory.get('nvram_with_ref_storage', 0)
|
||||||
|
load_file = max(0, int(load_file_bytes or 0))
|
||||||
|
method_component = report.get('method_component_size', 0)
|
||||||
return {
|
return {
|
||||||
'cap_version': report.get('cap_version'),
|
'cap_version': report.get('cap_version'),
|
||||||
'package_version': report.get('package_version'),
|
'package_version': report.get('package_version'),
|
||||||
@@ -1643,7 +1656,8 @@ def memory_json(report, memory):
|
|||||||
'class_count': report.get('class_count', 0),
|
'class_count': report.get('class_count', 0),
|
||||||
'method_count': report.get('method_count', 0),
|
'method_count': report.get('method_count', 0),
|
||||||
'code': {
|
'code': {
|
||||||
'method_component': report.get('method_component_size', 0),
|
'method_component': method_component,
|
||||||
|
'load_file': load_file,
|
||||||
},
|
},
|
||||||
'nvram': {
|
'nvram': {
|
||||||
'static_image': memory.get('nvram_static_image', 0),
|
'static_image': memory.get('nvram_static_image', 0),
|
||||||
@@ -1651,8 +1665,11 @@ def memory_json(report, memory):
|
|||||||
'install_objects': memory.get('nvram_persistent_objects', 0),
|
'install_objects': memory.get('nvram_persistent_objects', 0),
|
||||||
'header_overhead': memory.get('nvram_object_header_overhead', 0),
|
'header_overhead': memory.get('nvram_object_header_overhead', 0),
|
||||||
'ref_storage': memory.get('reference_storage', 0),
|
'ref_storage': memory.get('reference_storage', 0),
|
||||||
'total': memory.get('nvram_with_ref_storage', 0),
|
'total': nvram_total,
|
||||||
'runtime': memory.get('runtime_persistent_bytes', 0),
|
'runtime': memory.get('runtime_persistent_bytes', 0),
|
||||||
|
# C6+C8-style total: package image + persistent data (None when
|
||||||
|
# the caller did not provide the load file size)
|
||||||
|
'requirement': (load_file + nvram_total) if load_file else None,
|
||||||
},
|
},
|
||||||
'ram': {
|
'ram': {
|
||||||
'transient_arrays': ram_transient,
|
'transient_arrays': ram_transient,
|
||||||
@@ -1661,9 +1678,9 @@ def memory_json(report, memory):
|
|||||||
'total': ram_transient + ram_runtime + ram_frame,
|
'total': ram_transient + ram_runtime + ram_frame,
|
||||||
},
|
},
|
||||||
'suggested': {
|
'suggested': {
|
||||||
'c6': report.get('method_component_size', 0),
|
'c6': load_file or method_component,
|
||||||
'c7': ram_transient + 256,
|
'c7': ram_transient + 256,
|
||||||
'c8': memory.get('nvram_with_ref_storage', 0),
|
'c8': nvram_total,
|
||||||
},
|
},
|
||||||
'warnings': list(report.get('warnings', [])),
|
'warnings': list(report.get('warnings', [])),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
|
|||||||
from osmocom.utils import rpad
|
from osmocom.utils import rpad
|
||||||
|
|
||||||
|
|
||||||
VERSION = '3.5.14'
|
VERSION = '3.5.15'
|
||||||
|
|
||||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||||
|
|
||||||
@@ -2564,7 +2564,9 @@ def _cap_info_body(body):
|
|||||||
return {'ok': False, 'error': 'cap parse failed: %s' % e}
|
return {'ok': False, 'error': 'cap parse failed: %s' % e}
|
||||||
try:
|
try:
|
||||||
report, memory = capmem.analyze_bytes(bytes.fromhex(cap_hex))
|
report, memory = capmem.analyze_bytes(bytes.fromhex(cap_hex))
|
||||||
info = capmem.memory_json(report, memory)
|
# the load file (all components) is our proxy for the GP "non-volatile
|
||||||
|
# code" requirement, so the JSON can report the C6+C8-style total
|
||||||
|
info = capmem.memory_json(report, memory, load_file_bytes=len(loadfile_data) // 2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {'ok': False, 'error': 'cap analysis failed: %s' % e}
|
return {'ok': False, 'error': 'cap analysis failed: %s' % e}
|
||||||
return {'ok': True, 'load_file_aid': loadfile_aid, 'module_aid': module_aid,
|
return {'ok': True, 'load_file_aid': loadfile_aid, 'module_aid': module_aid,
|
||||||
|
|||||||
@@ -198,6 +198,18 @@ class TestCapAnalyzer(unittest.TestCase):
|
|||||||
self.assertEqual(info['suggested']['c8'], memory['nvram_with_ref_storage'])
|
self.assertEqual(info['suggested']['c8'], memory['nvram_with_ref_storage'])
|
||||||
self.assertEqual(info['warnings'], [])
|
self.assertEqual(info['warnings'], [])
|
||||||
self.assertIn('applets', info)
|
self.assertIn('applets', info)
|
||||||
|
# without the load file the tool's bytecode-only C6 is kept and no
|
||||||
|
# C6+C8-style total is reported
|
||||||
|
self.assertEqual(info['code']['load_file'], 0)
|
||||||
|
self.assertIsNone(info['nvram']['requirement'])
|
||||||
|
|
||||||
|
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)
|
||||||
|
self.assertEqual(info['code']['load_file'], 1000)
|
||||||
|
self.assertEqual(info['suggested']['c6'], 1000)
|
||||||
|
self.assertEqual(info['nvram']['requirement'],
|
||||||
|
1000 + memory['nvram_with_ref_storage'])
|
||||||
|
|
||||||
def test_unknown_opcode_stops_the_scan_with_a_warning(self):
|
def test_unknown_opcode_stops_the_scan_with_a_warning(self):
|
||||||
bytecode = _method_header(1, 0, 0) + b'\xee' # 0xEE is not a JC 2.1 opcode
|
bytecode = _method_header(1, 0, 0) + b'\xee' # 0xEE is not a JC 2.1 opcode
|
||||||
@@ -224,6 +236,12 @@ class TestCapInfoBody(unittest.TestCase):
|
|||||||
self.assertTrue(resp['load_file_bytes'] > 0)
|
self.assertTrue(resp['load_file_bytes'] > 0)
|
||||||
self.assertEqual(resp['memory']['package_aid'], '0102030405')
|
self.assertEqual(resp['memory']['package_aid'], '0102030405')
|
||||||
self.assertTrue(resp['memory']['nvram']['total'] > 0)
|
self.assertTrue(resp['memory']['nvram']['total'] > 0)
|
||||||
|
# the endpoint feeds the load file size in: C6 = package image and
|
||||||
|
# nvram.requirement = code image + persistent data (GP C6+C8 style)
|
||||||
|
self.assertEqual(resp['memory']['code']['load_file'], resp['load_file_bytes'])
|
||||||
|
self.assertEqual(resp['memory']['suggested']['c6'], resp['load_file_bytes'])
|
||||||
|
self.assertEqual(resp['memory']['nvram']['requirement'],
|
||||||
|
resp['load_file_bytes'] + resp['memory']['nvram']['total'])
|
||||||
|
|
||||||
def test_bad_hex_and_corrupt_archives_are_rejected(self):
|
def test_bad_hex_and_corrupt_archives_are_rejected(self):
|
||||||
self.assertFalse(_cap_info_body({})['ok'])
|
self.assertFalse(_cap_info_body({})['ok'])
|
||||||
|
|||||||
Reference in New Issue
Block a user