fix: clarify the suggested quotas, drop the CAP-details notes (v3.6.33)

The suggested quota row mixed a decimal C6 with hex C7/C8, so C6=27246
could be misread as hex (about 6x off).  It now renders as one consistent
line via the pure capQuotaText(sug): decimal bytes with the hex in
parentheses (C6=27246 B (0x6A6E) . C7=270 B (0x010E) . C8=5143 B (0x1417)),
with a tooltip naming the tags (C6 = non-volatile code space / load file,
C7 = volatile RAM, C8 = non-volatile data).

The static "Notes" disclosure (NVRAM definition + "estimate only") is
removed from the CAP details box - the help (EN/RU, the Install Package
row) carries it now; only the dynamic scan warnings (e.g. unknown opcode)
stay visible, since they mean the estimate is partial.

Tests: the new formatting pinned, the removed notes asserted absent;
stylesheet rebuilt; 664 frontend / 504 Python green; version 3.6.33;
sw simple-v306.
This commit is contained in:
2026-09-28 23:57:59 +03:00
parent e820a7ed41
commit a935361ca6
7 changed files with 34 additions and 17 deletions
+1 -1
View File
@@ -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> <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 (расширенные ресурсы карты: число установленных приложений и свободная энергонезависимая/энергозависимая память). Если карта отвергает компактный формат листинга (<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> <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> остаётся недоступной до успешного анализа. Оценка носит справочный характер и никогда не используется при установке: NVRAM — это образ кода плюс постоянные данные (сумма в стиле C6+C8 по GP Table 11-48; управление памятью карты, округление и запись в реестре не учитываются, а образ статических полей входит в обе части); рекомендуемый <strong>C6</strong> следует за load-файлом, а рекомендуемые квоты — это десятичные значения в байтах (шестнадцатеричное значение в скобках — это кодирование параметров установки C7/C8: C6 — энергонезависимый код, C7 — энергозависимая RAM, C8 — энергонезависимые данные).</td></tr>
</tbody> </tbody>
</table> </table>
+1 -1
View File
@@ -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> <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 (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 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> <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. 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 &mdash; 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> </tbody>
</table> </table>
+17 -7
View File
@@ -1714,7 +1714,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.6.32'; const SIMPLE_VERSION = '3.6.33';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching ===== // ===== Tab switching =====
@@ -8326,10 +8326,21 @@ function capMemBytes(n) {
// Estimate box body; pure apart from esc()/t(). `mem` is the server's // Estimate box body; pure apart from esc()/t(). `mem` is the server's
// memory object (pysim_simple_server.capmem.memory_json). // memory object (pysim_simple_server.capmem.memory_json).
// Suggested C6/C7/C8 quotas as one readable line: decimal bytes (what the
// install form takes and what GP 9.7 encodes) with the hex equivalent in
// parentheses (how the C7/C8 install parameters are coded). C6 = non-volatile
// code space (the load file), C7 = volatile (RAM), C8 = non-volatile data.
function capQuotaText(sug) {
sug = sug || {};
const dec = v => String(Number(v) || 0);
const hex = v => '0x' + (Number(v) || 0).toString(16).toUpperCase().padStart(4, '0');
return 'C6=' + dec(sug.c6) + ' B (' + hex(sug.c6) + ') \u00b7 C7=' + dec(sug.c7)
+ ' B (' + hex(sug.c7) + ') \u00b7 C8=' + dec(sug.c8) + ' B (' + hex(sug.c8) + ')';
}
function capMemHtml(mem) { 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 methodBytes = Number(code.method_component) || 0; const methodBytes = Number(code.method_component) || 0;
// older server responses carry only the bytecode size: fall back to it // older server responses carry only the bytecode size: fall back to it
const loadFile = Number(code.load_file) || methodBytes; const loadFile = Number(code.load_file) || methodBytes;
@@ -8424,7 +8435,8 @@ function capMemHtml(mem) {
t('peak method frame') + ' ' + capMemBytes(ram.peak_frame), t('peak method frame') + ' ' + capMemBytes(ram.peak_frame),
].join(' \u00b7 '))))); ].join(' \u00b7 ')))));
mrows.push(row(label(esc(t('Suggested quotas (informational)'))) + cell( mrows.push(row(label(esc(t('Suggested quotas (informational)'))) + cell(
'C6=' + esc(String(Number(sug.c6) || 0)) + ' C7=0x' + esc(hex2(sug.c7)) + ' C8=0x' + esc(hex2(sug.c8)), '<span title="' + esc(t('C6 = non-volatile code space (the load file); C7 = volatile (RAM); C8 = non-volatile data. Values are bytes; the hex is the install parameter coding.')) + '">'
+ esc(capQuotaText(sug)) + '</span>',
'pl-4 font-mono', 2))); 'pl-4 font-mono', 2)));
html += section(t('Memory')) + table(mrows.join('')); html += section(t('Memory')) + table(mrows.join(''));
@@ -8446,10 +8458,8 @@ function capMemHtml(mem) {
html += disclosure(t('Components') + ' (' + comps.length + ')') + table(crows) + '</details>'; html += disclosure(t('Components') + ' (' + comps.length + ')') + table(crows) + '</details>';
} }
html += disclosure(t('Notes')) + // Scan warnings only - the NVRAM definition and the "estimate only" note
'<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>' + // live in the help; the CAP details box stays compact (v3.6.33).
'<div class="text-gray-400">' + esc(t('Estimate only — memory values are not used for installation')) + '</div>' +
'</details>';
(mem.warnings || []).forEach(w => { (mem.warnings || []).forEach(w => {
html += '<div class="text-amber-600 dark:text-amber-400">\u26a0 ' + esc(w) + '</div>'; html += '<div class="text-amber-600 dark:text-amber-400">\u26a0 ' + esc(w) + '</div>';
}); });
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v305'; const CACHE = 'simple-v306';
const URLS = [ const URLS = [
'index.html', 'index.html',
'help.html', 'help.html',
+12 -5
View File
@@ -39,6 +39,7 @@ eval(extractFunc(html, 'jcAidNorm'));
eval(extractFunc(html, 'jcAidName')); eval(extractFunc(html, 'jcAidName'));
eval(extractFunc(html, 'jcAidFamily')); eval(extractFunc(html, 'jcAidFamily'));
eval(extractFunc(html, 'capMemBytes')); eval(extractFunc(html, 'capMemBytes'));
eval(extractFunc(html, 'capQuotaText'));
eval(extractFunc(html, 'capMemHtml')); eval(extractFunc(html, 'capMemHtml'));
eval(extractFunc(html, 'capAnalyzeFile', true)); eval(extractFunc(html, 'capAnalyzeFile', true));
// The RAM CAP analysis also applies the detected toolkit mode (DOM-bound; its // The RAM CAP analysis also applies the detected toolkit mode (DOM-bound; its
@@ -93,9 +94,9 @@ test('capMemHtml renders the grouped report: package, requires, memory, componen
assert.ok(out.includes('>Requires (1)</div>'), out); assert.ok(out.includes('>Requires (1)</div>'), out);
assert.ok(out.includes('>Memory</div>'), out); assert.ok(out.includes('>Memory</div>'), out);
assert.ok(out.includes('>Components (3)'), out); assert.ok(out.includes('>Components (3)'), out);
assert.ok(out.includes('>Notes'), out); // the static notes moved to the help; only scan warnings stay in the box
assert.ok(out.includes('Table 11-48'), out); assert.ok(!out.includes('>Notes'), out);
assert.ok(out.includes('Estimate only'), out); assert.ok(!out.includes('Table 11-48'), out);
assert.ok(out.includes('unknown opcode 0xAA'), out); assert.ok(out.includes('unknown opcode 0xAA'), out);
// memory: the C6+C8-style total once, with indented children // memory: the C6+C8-style total once, with indented children
assert.ok(out.includes('NVRAM requirement'), out); assert.ok(out.includes('NVRAM requirement'), out);
@@ -109,7 +110,7 @@ test('capMemHtml renders the grouped report: package, requires, memory, componen
assert.ok(out.includes('reference storage 8 B'), out); assert.ok(out.includes('reference storage 8 B'), out);
assert.ok(out.includes('>24 B</td>'), out); assert.ok(out.includes('>24 B</td>'), 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=3000 C7=0x0110 C8=0x0088'), out); assert.ok(out.includes('C6=3000 B (0x0BB8) \u00b7 C7=272 B (0x0110) \u00b7 C8=136 B (0x0088)'), out);
// requires: library name, AID, minimum version, family and refs // requires: library name, AID, minimum version, family and refs
const req = out.slice(out.indexOf('Requires (1)'), out.indexOf('Memory</div>')); const req = out.slice(out.indexOf('Requires (1)'), out.indexOf('Memory</div>'));
assert.ok(req.includes('>AID</td>'), req); assert.ok(req.includes('>AID</td>'), req);
@@ -173,7 +174,13 @@ test('capMemHtml drops the components section without a component list', () => {
delete mem.components; delete mem.components;
const out = capMemHtml(mem); const out = capMemHtml(mem);
assert.ok(!out.includes('>Components'), out); assert.ok(!out.includes('>Components'), out);
assert.ok(out.includes('>Notes'), out); assert.ok(out.includes('>Memory</div>'), out);
});
test('capQuotaText renders decimal bytes with the hex coding', () => {
const txt = capQuotaText({ c6: 27246, c7: 270, c8: 5143 });
assert.strictEqual(txt, 'C6=27246 B (0x6A6E) \u00b7 C7=270 B (0x010E) \u00b7 C8=5143 B (0x1417)');
assert.strictEqual(capQuotaText({}), 'C6=0 B (0x0000) \u00b7 C7=0 B (0x0000) \u00b7 C8=0 B (0x0000)');
}); });
test('capGateOk gates the RAM install op and the scripts form', () => { test('capGateOk gates the RAM install op and the scripts form', () => {
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pysim-simple-server" name = "pysim-simple-server"
version = "3.6.32" version = "3.6.33"
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.
+1 -1
View File
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad from osmocom.utils import rpad
VERSION = '3.6.32' VERSION = '3.6.33'
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