diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 40b9541..968e829 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -319,7 +319,7 @@ ОперацияОписание Обзор карты (все данные GP)Запрос GET STATUS для ISD, приложений, ELF и модулей ELF, а также GET DATA FF21 для информации о памяти. Результаты отображаются в обзоре с кнопками Удалить для каждого элемента. - Установка пакета (.cap файл)Отправка .cap файла на карту через сервер: INSTALL[for load] → LOAD ×N → INSTALL[for install (+make selectable)]. Load-файл делится на LOAD APDU размера размер блока LOAD (1–240 байт полезной нагрузки, по умолчанию 240); каждый защищённый пакет доставляется одним SMS или, если он больше, конкатенированной SMS-PP загрузкой до 5 сегментов, так что большой .cap просто занимает несколько SMS. Сразу после выбора файл проверяется и в форме показывается оценка требований к памяти: требование NVRAM (образ кода + постоянные данные, сумма в стиле C6+C8 из GP Card Spec) и оценка RAM. Там же перечислены библиотеки, с которыми слинкован CAP (его компонент Import, в виде имя ≥ версия — имена стандартных библиотек подставляются, а где известно, даётся подсказка о версии Java Card), идентификаторы пакета и апплетов и разбивка по компонентам. Повреждённый файл не проходит анализ, и Execute остаётся недоступной до успешного анализа. + Установка пакета (.cap файл)Отправка .cap файла на карту через сервер: INSTALL[for load] → LOAD ×N → INSTALL[for install (+make selectable)]. Load-файл делится на LOAD APDU размера размер блока LOAD (1–240 байт полезной нагрузки, по умолчанию 240); каждый защищённый пакет доставляется одним SMS или, если он больше, конкатенированной SMS-PP загрузкой до 5 сегментов, так что большой .cap просто занимает несколько SMS. Сразу после выбора файл проверяется и в форме показывается оценка требований к памяти: требование NVRAM (образ кода + постоянные данные, сумма в стиле C6+C8 из GP Card Spec) и оценка RAM. Там же показаны библиотеки, с которыми слинкован CAP (его компонент Import, в виде имя ≥ версия — имена стандартных библиотек подставляются, а где известно, даётся подсказка о версии Java Card), идентификаторы пакета и апплетов и разбивка по компонентам (размеры и доли в порядке load-файла) — всё сгруппировано по разделам Package, Requires, Memory и Components. Повреждённый файл не проходит анализ, и Execute остаётся недоступной до успешного анализа. @@ -376,7 +376,7 @@

Таблица показывает имя, тип, число APDU и время создания каждого скрипта, а также кнопки Редактировать и Удалить; в редакторе есть поле имени и текстовая область APDU. В ходе сессии сервер отдаёт по одному C-APDU на каждый POST карты и отслеживает выполнение: карта сообщает статус в следующем POST (X-Admin-Script-Status), оборвавшаяся сессия досылает только невыполненные APDU (X-Admin-Resume продолжает, новый диалог начинает заново), а завершённый скрипт закрывается ответом 204 No Content. Конструктор RAM/GP вкладки Remote APDU может отправить цепочку команд прямо в прогон кнопкой «В очередь SCP81».

diff --git a/frontend/help.html b/frontend/help.html index 4117bb8..add1da8 100644 --- a/frontend/help.html +++ b/frontend/help.html @@ -318,7 +318,7 @@ OperationDescription Explore Card (all GP data)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 Delete buttons. - Install Package (.cap file)Sends a .cap 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 LOAD block size (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 .cap simply takes several SMS. As soon as the file is selected it is validated and its memory requirements are estimated in the form: the NVRAM requirement (the code image + persistent data, the GlobalPlatform Card Spec C6+C8 style total) and the RAM estimate. The same box lists the libraries the CAP is linked against (its Import component, shown as name ≥ version with standard library names resolved and a Java Card SDK hint where known) plus the package/applet identity and the component breakdown. A corrupt or wrong-format file fails the analysis, and Execute stays disabled until a successful analysis. + Install Package (.cap file)Sends a .cap 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 LOAD block size (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 .cap simply takes several SMS. As soon as the file is selected it is validated and its memory requirements are estimated in the form: the NVRAM requirement (the code image + persistent data, the GlobalPlatform Card Spec C6+C8 style total) and the RAM estimate. The same box reports the libraries the CAP is linked against (its Import component, shown as name ≥ version with standard library names 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 Package, Requires, Memory and Components headings. A corrupt or wrong-format file fails the analysis, and Execute stays disabled until a successful analysis. @@ -375,7 +375,7 @@

The table lists each script with its kind, APDU count and creation time, plus Edit and Delete; 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 (X-Admin-Script-Status), a session that dies resends only the unexecuted APDUs (X-Admin-Resume continues, a fresh dialog restarts), and a completed script is closed with 204 No Content. The Remote APDU tab's RAM/GP builder can feed a command chain straight into the run with Queue in SCP81.

diff --git a/frontend/index.html b/frontend/index.html index 2cbe277..6f85d8d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -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.16'; +const SIMPLE_VERSION = '3.5.17'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== @@ -8032,83 +8032,116 @@ function capMemHtml(mem) { const requirement = Number(nv.requirement) || (loadFile + nvramData); const otherComponents = Math.max(0, loadFile - methodBytes); const imports = mem.imports || []; + const applets = mem.applets || []; + const comps = mem.components || []; + const bytes = v => esc(capMemBytes(v)); + // the report is a set of one-heading tables: labels and column heads are + // muted, numbers line up right-aligned in a monospace column + const cell = (content, cls, span) => '' + content + ''; + const head = (content, cls) => '' + content + ''; + const table = rows => '' + rows + '
'; + const row = cells => '' + cells + ''; + const label = (content, cls) => cell(content, 'whitespace-nowrap' + (cls ? ' ' + cls : '')); + const num = content => cell(content, 'pl-4 text-right whitespace-nowrap font-mono'); + const part = content => cell(content, 'pl-4 text-gray-400 dark:text-slate-500'); + const section = content => '
' + esc(content) + '
'; + const disclosure = content => '
' + esc(content) + ''; let html = '
' + esc(t('CAP requirements (estimate)')) + '
'; - // the libraries the CAP is linked against (JC VM spec 6.6), shown as - // ">=" requirements: same major, minor >= the export version (4.5.2) - if (imports.length) { - const req = imports.map(im => { - const name = jcAidName(im.aid) || im.aid; - return name + ' \u2265 ' + (Number(im.major) || 0) + '.' + (Number(im.minor) || 0); - }).join(' \u00b7 '); - html += '
' + esc(t('Requires:')) + ' ' + esc(req) + '
'; + + // ── package identity: AIDs, header flags (Table 6-4), applets and the + // compiled-against hint (javacard.framework version + CAP format) ── + let pkgRows = ''; + if (mem.package_aid) { + let val = esc(mem.package_aid); + if (mem.package_name) val += '
' + esc(mem.package_name) + '
'; + pkgRows += row(label(esc(t('Package'))) + cell(val, 'pl-4 font-mono break-all')); } - // 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 += '
' + 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)) + '
'; - html += '
' + esc(t('Details')) + ''; - // compiled-against hint from the javacard.framework version + CAP format - 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 : ''; - if (mem.cap_version) against += (against ? ' · ' : '') + t('CAP format') + ' ' + mem.cap_version; - if (against) html += '
' + esc(t('Compiled against:')) + ' ' + esc(against) + '
'; - if (imports.length) { - imports.forEach(im => { - const name = jcAidName(im.aid); - const fam = jcAidFamily(im.aid); - let line = (name || im.aid) + ' ' + (Number(im.major) || 0) + '.' + (Number(im.minor) || 0); - if (fam) line += ' — ' + fam; - line += ' — ' + (Number(im.refs) || 0) + ' ' + t('refs'); - if (name) line += ' — ' + im.aid; - html += '
' + esc(line) + '
'; - }); + if (mem.package_version) { + pkgRows += row(label(esc(t('Version'))) + cell(esc('v' + mem.package_version), 'pl-4 font-mono')); } - // package identity + header flags (Table 6-4) + applets - let pkg = esc(mem.package_aid || '?') + (mem.package_version ? ' v' + esc(mem.package_version) : ''); const fl = mem.flags || {}; const fls = []; if (fl.applet) fls.push(t('applet package')); if (fl.export) fls.push(t('exports an API')); if (fl.int) fls.push(t('uses int')); - if (fls.length) pkg += ' (' + fls.join(', ') + ')'; - if (mem.package_name) pkg += ' — ' + esc(mem.package_name); - html += '
' + esc(t('Package:')) + ' ' + pkg + '
'; - if ((mem.applets || []).length) { - html += '
' + esc(t('Applets:')) + ' ' + esc((mem.applets || []).map(a => { + if (fls.length) pkgRows += row(label(esc(t('Type'))) + cell(esc(fls.join(' \u00b7 ')), 'pl-4')); + if (applets.length) { + pkgRows += row(label(esc(t('Applets'))) + cell(applets.map(a => { const n = jcAidName(a); - return n ? a + ' (' + n + ')' : a; - }).join(', ')) + '
'; + return esc(a) + (n ? ' (' + esc(n) + ')' : ''); + }).join('
'), 'pl-4 font-mono break-all')); } - const comps = mem.components || []; + 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 : ''; + 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 (pkgRows) html += section(t('Package')) + table(pkgRows); + + // ── the libraries the CAP is linked against (JC VM spec 6.6), shown as + // ">=" requirements: same major, minor >= the export version (4.5.2); + // refs = distinct constant-pool references to the package (6.7) ── + if (imports.length) { + let reqRows = row(head('', 'text-left') + head(esc(t('Version')), 'pl-4 text-right whitespace-nowrap') + + head(esc(t('Family')), 'pl-4 text-left') + head(esc(t('Refs')), 'pl-4 text-right whitespace-nowrap')); + imports.forEach(im => { + const name = jcAidName(im.aid); + const fam = jcAidFamily(im.aid); + reqRows += row(cell(name ? esc(name) : '' + esc(im.aid) + '', 'whitespace-nowrap') + + num('\u2265 ' + (Number(im.major) || 0) + '.' + (Number(im.minor) || 0)) + + part(esc(fam)) + num(String(Number(im.refs) || 0))); + }); + html += section(t('Requires') + ' (' + imports.length + ')') + table(reqRows); + } + + // ── memory: the NVRAM requirement is the C6+C8-style total (GP Card + // Spec v2.3.1 Table 11-48) of the code image (load file) and the + // persistent data; children are indented under their total ── + const mrows = []; + mrows.push(row(label(esc(t('NVRAM requirement'))) + num(bytes(requirement)) + + part(esc(t('code image') + ' + ' + t('data'))))); + mrows.push(row(label(esc(t('code image (load file)')), 'pl-4') + num(bytes(loadFile)) + + part(esc('Method.cap ' + capMemBytes(methodBytes) + ' \u00b7 ' + t('other components') + ' ' + capMemBytes(otherComponents))))); + mrows.push(row(label(esc(t('persistent data')), 'pl-4') + num(bytes(nvramData)) + part(esc([ + t('static image') + ' ' + capMemBytes(nv.static_image), + t('static array init') + ' ' + capMemBytes(nv.array_init), + t('install-time objects') + ' ' + capMemBytes(nv.install_objects), + t('object headers') + ' ' + capMemBytes(nv.header_overhead), + t('reference storage') + ' ' + capMemBytes(nv.ref_storage), + ].join(' \u00b7 '))))); + mrows.push(row(label(esc(t('RAM (volatile)'))) + num(bytes(ram.total)) + part(esc([ + t('transient arrays') + ' ' + capMemBytes(ram.transient_arrays), + t('runtime transient') + ' ' + capMemBytes(ram.runtime_transient), + t('peak method frame') + ' ' + capMemBytes(ram.peak_frame), + ].join(' \u00b7 '))))); + 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)), + 'pl-4 font-mono', 2))); + html += section(t('Memory')) + table(mrows.join('')); + + // ── component sizes in load-file order (incl. the Directory/Export + // entries capmem does not parse) with each entry's share of the load file if (comps.length) { const ctotal = loadFile || comps.reduce((sum, c) => sum + (Number(c.size) || 0), 0); - html += '
' + esc(t('Components:')) + ' ' + comps.map(c => { + const tcls = 'border-t border-gray-200 dark:border-slate-700'; + let crows = row(head(esc(t('Component')), 'text-left') + head(esc(t('Size')), 'pl-4 text-right whitespace-nowrap') + + head(esc(t('Share')), 'pl-4 text-right whitespace-nowrap')); + comps.forEach(c => { const sz = Number(c.size) || 0; - const pct = ctotal ? ' (' + Math.round(sz * 100 / ctotal) + '%)' : ''; - return esc(c.name + ' ' + capMemBytes(sz) + pct); - }).join(' · ') + '
'; + crows += row(label(esc(c.name)) + num(bytes(sz)) + + num(ctotal ? Math.round(sz * 100 / ctotal) + '%' : '\u2014')); + }); + crows += row(cell(esc(t('Load file (total)')), tcls) + + cell(bytes(loadFile), 'pl-4 text-right whitespace-nowrap font-mono ' + tcls) + + cell('100%', 'pl-4 text-right whitespace-nowrap font-mono ' + tcls)); + html += disclosure(t('Components') + ' (' + comps.length + ')') + table(crows) + '
'; } - html += '
' + esc(t('Code image (load file):')) + ' ' + esc(capMemBytes(loadFile)) + - ' · ' + esc(t('bytecode (Method.cap):')) + ' ' + esc(capMemBytes(methodBytes)) + - ' · ' + esc(t('other components:')) + ' ' + esc(capMemBytes(otherComponents)) + '
'; - html += '
' + 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)) + '
'; - html += '
' + 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)) + '
'; - html += '
' + 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)) + '
'; - html += '
' + 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.')) + '
'; - html += '
' + esc(t('Estimate only — memory values are not used for installation')) + '
'; - html += '
'; + + html += disclosure(t('Notes')) + + '
' + 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.')) + '
' + + '
' + esc(t('Estimate only — memory values are not used for installation')) + '
' + + ''; (mem.warnings || []).forEach(w => { html += '
\u26a0 ' + esc(w) + '
'; }); @@ -16140,6 +16173,33 @@ const LANG_RU = { 'Peak method frame:': 'Пиковый кадр метода:', 'Suggested install quotas (informational):': 'Рекомендуемые квоты установки (справочно):', 'Estimate only — memory values are not used for installation': 'Только оценка — значения памяти не используются при установке', + 'Package': 'Пакет', + 'Version': 'Версия', + 'Type': 'Тип', + 'Applets': 'Апплеты', + 'Compiled': 'Собрано', + 'Requires': 'Требуются', + 'Family': 'Семейство', + 'Refs': 'Ссылки', + 'NVRAM requirement': 'Требование NVRAM', + 'code image (load file)': 'образ кода (load-файл)', + 'persistent data': 'постоянные данные', + 'RAM (volatile)': 'ОЗУ (volatile)', + 'other components': 'прочие компоненты', + 'static image': 'статический образ', + 'static array init': 'инициализация массивов', + 'install-time objects': 'объекты установки', + 'object headers': 'заголовки объектов', + 'reference storage': 'хранение ссылок', + 'transient arrays': 'transient-массивы', + 'runtime transient': 'transient во время работы', + 'peak method frame': 'пиковый кадр', + 'Suggested quotas (informational)': 'Рекомендуемые квоты (справочно)', + 'Components': 'Компоненты', + 'Component': 'Компонент', + 'Share': 'Доля', + 'Load file (total)': 'Load-файл (итого)', + 'Notes': 'Примечания', 'Step': 'Шаг', 'Install OK': 'Установка OK', 'Install FAILED at step:': 'Установка не удалась на шаге:', diff --git a/frontend/sw.js b/frontend/sw.js index 0683fd5..7ca1e35 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v269'; +const CACHE = 'simple-v270'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/capmem.test.js b/frontend/tests/capmem.test.js index 02139c0..aa8b01c 100644 --- a/frontend/tests/capmem.test.js +++ b/frontend/tests/capmem.test.js @@ -79,33 +79,54 @@ test('capMemBytes formats bytes and kilobytes', () => { assert.strictEqual(capMemBytes(null), '0 B'); }); -test('capMemHtml renders the NVRAM requirement, breakdown and warnings', () => { +test('capMemHtml renders the grouped report: package, requires, memory, components, 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); - // 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=3000 C7=0x0110 C8=0x0088'), out); + // grouped, labelled sections (always visible except components/notes) + assert.ok(out.includes('>Package'), out); + assert.ok(out.includes('>Requires (1)'), out); + assert.ok(out.includes('>Memory'), out); + assert.ok(out.includes('>Components (3)'), out); + assert.ok(out.includes('>Notes'), out); assert.ok(out.includes('Table 11-48'), out); assert.ok(out.includes('Estimate only'), out); assert.ok(out.includes('unknown opcode 0xAA'), out); - // the required libraries with the family, reference count and the - // compiled-against hint derived from the framework version - assert.ok(out.includes('Requires: javacard.framework \u2265 1.0'), out); - assert.ok(out.includes('Compiled against: Java Card 2.1.1/2.1.2 \u00b7 CAP format 2.1'), out); - assert.ok(out.includes('javacard.framework 1.0 — Oracle JavaCard API — 6 refs — A0000000620101'), out); - assert.ok(out.includes('Package: A0000000620101 v1.0 (applet package)'), out); - assert.ok(out.includes('Applets: A0000000620101 (javacard.framework)'), out); - assert.ok(out.includes('Components: Header 20 B (1%) \u00b7 Method 2.7 kB (93%) \u00b7 ConstantPool 180 B (6%)'), out); + // memory: the C6+C8-style total once, with indented children + assert.ok(out.includes('NVRAM requirement'), out); + assert.ok(out.includes('>3.1 kB'), out); + assert.ok(out.includes('code image + data'), out); + assert.ok(out.includes('>2.9 kB'), out); + assert.ok(out.includes('Method.cap 2.0 kB'), out); + assert.ok(out.includes('other components 952 B'), out); + assert.ok(out.includes('>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('>24 B'), out); + assert.ok(out.includes('peak method frame 8 B'), out); + assert.ok(out.includes('C6=3000 C7=0x0110 C8=0x0088'), out); + // requires: library name, version, family and refs; the AID is not + // repeated when the name resolves (it adds nothing), and the section + // carries no package AIDs + assert.ok(out.includes('>javacard.framework'), out); + assert.ok(out.includes('>\u2265 1.0'), out); + assert.ok(out.includes('>Oracle JavaCard API'), out); + assert.ok(out.includes('>6'), out); + const req = out.slice(out.indexOf('Requires (1)'), out.indexOf('Memory')); + assert.ok(!req.includes('A0000000620101'), req); + // package identity + compiled-against hint derived from the framework version + assert.ok(out.includes('A0000000620101') && out.includes('>v1.0'), out); + assert.ok(out.includes('applet package'), out); + assert.ok(out.includes('(javacard.framework)'), out); + assert.ok(out.includes('Java Card 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') && out.includes('>20 B') && out.includes('>1%'), out); + assert.ok(out.includes('>Method') && out.includes('>2.7 kB') && out.includes('>93%'), out); + assert.ok(out.includes('>ConstantPool') && out.includes('>180 B') && out.includes('>6%'), out); + assert.ok(out.includes('Load file (total)') && out.includes('>100%'), out); + const c1 = out.indexOf('Header'), c2 = out.indexOf('Method'), c3 = out.indexOf('ConstantPool'); + assert.ok(c1 >= 0 && c1 < c2 && c2 < c3, 'components keep the load-file order'); assert.strictEqual(capMemHtml(null), ''); }); @@ -115,17 +136,23 @@ test('capMemHtml skips the import section on a response without imports', () => mem.code = { method_component: 2048 }; delete mem.nvram.requirement; const out = capMemHtml(mem); - assert.ok(!out.includes('Requires:'), out); - assert.ok(out.includes('NVRAM requirement \u2248 2.1 kB'), out); + assert.ok(!out.includes('>Requires'), out); + assert.ok(!out.includes('Java Card'), out); // no framework import -> no SDK hint + assert.ok(out.includes('CAP format 2.1'), out); + assert.ok(out.includes('NVRAM requirement'), out); + assert.ok(out.includes('>2.1 kB'), out); }); test('capMemHtml keeps unknown import AIDs bare and labels the family only when known', () => { const mem = memFixture(); mem.imports = [{ aid: 'A1130001180001', minor: 0, major: 1, refs: 2 }]; const out = capMemHtml(mem); - assert.ok(out.includes('Requires: A1130001180001 \u2265 1.0'), out); - assert.ok(out.includes('A1130001180001 1.0 — 2 refs'), out); - assert.ok(!out.includes('Compiled against: Java Card'), out); // no framework import + const req = out.slice(out.indexOf('Requires (1)'), out.indexOf('Memory')); + assert.ok(req.includes('A1130001180001'), req); // unknown AID: shown as the name + assert.ok(req.includes('>\u2265 1.0'), req); + assert.ok(req.includes('>2'), req); // refs + assert.ok(!out.includes('Java Card'), out); // no framework import + assert.ok(out.includes('CAP format 2.1'), out); }); test('capMemHtml falls back to the bytecode size on older server responses', () => { @@ -133,7 +160,16 @@ test('capMemHtml falls back to the bytecode size on older server responses', () 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); + assert.ok(out.includes('NVRAM requirement'), out); + assert.ok(out.includes('>2.1 kB'), out); +}); + +test('capMemHtml drops the components section without a component list', () => { + const mem = memFixture(); + delete mem.components; + const out = capMemHtml(mem); + assert.ok(!out.includes('>Components'), out); + assert.ok(out.includes('>Notes'), out); }); test('capGateOk gates the RAM install op and the scripts form', () => { diff --git a/pyproject.toml b/pyproject.toml index 7b4175c..83b25e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.5.16" +version = "3.5.17" 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. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index fad44bc..c9a0250 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.5.16' +VERSION = '3.5.17' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE