fix: explain the FF21 resource block and move the format line last (v3.6.27)

The explorer's FF21 block was titled "Memory (GET DATA FF21)" although the
tag is the Extended Card Resources Information (GP tag FF21, TS 102 226
8.2.1.7.2: installed applications, free non-volatile and free volatile
memory), and the detected RAM command format line sat between the Free NV
and Free Volatile lines.

- Heading is now "Card resources (GET DATA FF21)" with a tooltip naming the
  tag and its three fields (EN/RU).
- The "RAM command format:" line moved after the resource lines.
- Help EN/RU spells out what FF21 returns; test asserts the format line
  follows the resource lines.

657 frontend / 504 Python green; version 3.6.27; sw simple-v300.
This commit is contained in:
2026-09-28 23:18:00 +03:00
parent f65a512440
commit 85ba2a709d
7 changed files with 21 additions and 10 deletions
+1 -1
View File
@@ -320,7 +320,7 @@
<table class="w-full text-sm mb-3 border-collapse">
<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 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] &rarr; LOAD &times;N &rarr; INSTALL[for install (+make selectable)]. Load-файл делится на LOAD APDU размера <strong>размер блока LOAD</strong> (1&ndash;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> остаётся недоступной до успешного анализа.</td></tr>
</tbody>
</table>
+1 -1
View File
@@ -319,7 +319,7 @@
<table class="w-full text-sm mb-3 border-collapse">
<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. 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 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] &rarr; LOAD &times;N &rarr; INSTALL[for install (+make selectable)]. The load file is split into LOAD APDUs of the <strong>LOAD block size</strong> (1&ndash;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) &mdash; 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.</td></tr>
</tbody>
</table>
+6 -5
View File
@@ -1713,7 +1713,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.26';
const SIMPLE_VERSION = '3.6.27';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching =====
@@ -8843,11 +8843,11 @@ function ramRenderExploreHtml(mem, isd, apps, elfs, fmt) {
let html = '';
if (mem && (mem.appCount != null || mem.freeNV != null || mem.freeV != null)) {
html += '<div class="mb-4 p-3 bg-gray-50 dark:bg-slate-800 rounded">';
html += '<div class="font-semibold text-sm mb-1">' + esc(t('Memory (GET DATA FF21)')) + '</div>';
html += '<div class="font-semibold text-sm mb-1" title="' + esc(t('Extended Card Resources (GP tag FF21, ETSI TS 102 226 8.2.1.7.2): installed applications, free non-volatile and free volatile memory')) + '">' + esc(t('Card resources (GET DATA FF21)')) + '</div>';
html += '<div>' + esc(t('Applications:')) + ' ' + (mem.appCount != null ? mem.appCount : '?') + '</div>';
html += '<div>' + esc(t('Free NV:')) + ' ' + (mem.freeNV != null ? mem.freeNV + ' B' : '?') + '</div>';
if (fmt) html += '<div>' + esc(t('RAM command format:')) + ' ' + esc(fmt) + '</div>';
html += '<div>' + esc(t('Free Volatile:')) + ' ' + (mem.freeV != null ? mem.freeV + ' B' : '?') + '</div>';
if (fmt) html += '<div>' + esc(t('RAM command format:')) + ' ' + esc(fmt) + '</div>';
html += '</div>';
}
if (isd && isd.length) {
@@ -9039,7 +9039,7 @@ async function ramExplore(sp) {
if (collector.length === before && compactError && !quiet) errors.push(compactError);
}
ramShowProgress(t('Memory (GET DATA FF21)') + '...');
ramShowProgress(t('Card resources (GET DATA FF21)') + '...');
try {
const memRes = await ramSendOta('80CAFF2100', Object.assign({}, sp, { spi2: '01' }));
if (memRes.ram_format) sp.ram_format = memRes.ram_format;
@@ -17555,7 +17555,8 @@ const LANG_RU = {
'KIc key and KID key are required': 'Укажите ключ KIc и ключ KID',
'RAM command format:': 'Формат команд RAM:',
'Query expanded registry details (E3)': 'Запрашивать расширенные данные реестра (E3)',
'Memory (GET DATA FF21)': 'Память (GET DATA FF21)',
'Card resources (GET DATA FF21)': 'Ресурсы карты (GET DATA FF21)',
'Extended Card Resources (GP tag FF21, ETSI TS 102 226 8.2.1.7.2): installed applications, free non-volatile and free volatile memory': 'Расширенные ресурсы карты (тег GP FF21, ETSI TS 102 226 8.2.1.7.2): число установленных приложений, свободная энергонезависимая и свободная энергозависимая память',
'No secured packet to send. Generate a secure packet first.': 'Нет защищённого пакета для отправки. Сначала соберите пакет.',
'No secured packet to verify. Generate a secure packet first.': 'Нет защищённого пакета для проверки. Сначала соберите пакет.',
'Select': 'Выборка',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v299';
const CACHE = 'simple-v300';
const URLS = [
'index.html',
'help.html',
+10
View File
@@ -180,6 +180,16 @@ test('STORE DATA ram-enc P1 values 00/40/80/C0/E0', () => {
}
});
test('ramRenderExploreHtml puts the command format after the resource lines', () => {
global.t = s => s;
const out = ramRenderExploreHtml({ appCount: 5, freeNV: 100, freeV: 50 }, [], [], [], 'compact');
delete global.t;
const iNv = out.indexOf('Free NV:');
const iV = out.indexOf('Free Volatile:');
const iFmt = out.indexOf('RAM command format:');
assert.ok(iNv >= 0 && iV >= 0 && iFmt > iV, out);
});
test('ramRenderExploreHtml localizes every label and button', () => {
const seen = [];
global.t = s => { seen.push(s); return 'XX' + s; };