diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 895f8e6..4217da0 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -320,7 +320,7 @@ - + diff --git a/frontend/help.html b/frontend/help.html index 03c830e..987afb6 100644 --- a/frontend/help.html +++ b/frontend/help.html @@ -319,7 +319,7 @@
ОперацияОписание
Обзор карты (все данные GP)Запрос GET STATUS для ISD, приложений, ELF и модулей ELF, а также GET DATA FF21 (расширенные ресурсы карты: число установленных приложений и свободная энергонезависимая/энергозависимая память). Компактный листинг содержит только AID, состояние жизненного цикла и привилегии (версии в устаревшем формате нет); если карта отвергает его (P2.b2=0, устаревший по GP), запросы повторяются в расширенном формате E3, а флажок Запрашивать расширенные данные реестра (связи load-файла и SD) дополнительно запрашивает их и объединяет расширенные поля: версию исполняемого load-файла, AID load-файла, AID Security Domain, списки модулей, пункты меню STK апплета (SCP Registry Data, TS 102 226 8.2.1.6) и любые другие объекты реестра, которые вернула карта (показываются как есть, без потерь) — он стоит несколько дополнительных защищённых пакетов на каждый обзор и включён по умолчанию (запоминается в браузере; снимите флажок, чтобы пропустить). Карты различаются в том, что принимают, поэтому расширенный запрос пробуется в трёх формах — эталонный список тегов (с EA по TS 102 226), тот же список без EA и вовсе без списка тегов (форма GlobalPlatformPro) — а обзор сообщает, какая форма сработала и где выполнен откат к компактному списку (версии пакетов тогда недоступны). Результаты отображаются в обзоре (строка успеха минимальна, счётчики — в заголовках разделов) с кнопками Удалить для каждого элемента и определённым форматом команд RAM (подсказка при наведении объясняет compact и expanded).
Обзор карты (все данные GP)Запрос GET STATUS для ISD, приложений, ELF и модулей ELF, а также GET DATA FF21 (расширенные ресурсы карты: число установленных приложений и свободная энергонезависимая/энергозависимая память). Компактный листинг содержит только AID, состояние жизненного цикла и привилегии (версии в устаревшем формате нет); если карта отвергает его (P2.b2=0, устаревший по GP), запросы повторяются в расширенном формате E3, а флажок Запрашивать расширенные данные реестра (связи load-файла и SD) дополнительно запрашивает их и объединяет расширенные поля: версию исполняемого load-файла, AID load-файла, AID Security Domain, списки модулей, пункты меню STK апплета (SCP Registry Data, TS 102 226 8.2.1.6) и любые другие объекты реестра, которые вернула карта (показываются как есть, без потерь) — он стоит несколько дополнительных защищённых пакетов на каждый обзор и включён по умолчанию (запоминается в браузере; снимите флажок, чтобы пропустить). Карты различаются в том, что принимают, поэтому расширенный запрос пробуется в трёх формах — эталонный список тегов (с EA по TS 102 226), тот же список без EA и вовсе без списка тегов (форма GlobalPlatformPro) — а обзор сообщает, какая форма сработала и где выполнен откат к компактному списку (версии пакетов тогда недоступны). Если строки ELF всё ещё без версий, формы со списком тегов повторяются для обоих P1=10 и P1=20, пропуская уже отклонённые комбинации, и обход останавливается, как только версия найдена. Результаты отображаются в обзоре (строка успеха минимальна, счётчики — в заголовках разделов) с кнопками Удалить для каждого элемента и определённым форматом команд RAM (подсказка при наведении объясняет compact и expanded).
Установка пакета (.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: имя библиотеки, AID и минимальная версия ≥ x.y — имена стандартных библиотек и семейства подставляются, а где известно, даётся подсказка о версии Java Card), идентификаторы пакета и апплетов и разбивка по компонентам (размеры и доли в порядке load-файла) — всё сгруппировано по разделам Package, Requires, Memory и Components. Строка Platform показывает минимальный уровень Java Card, который должна предоставить карта (выводится из импорта javacard.framework; флаг int в заголовке дополнительно требует поддержки int — Java Card 2.2+), и релиз ETSI, из которого пришли импорты UICC API (версия uicc.toolkit → REL-N по корпусу sim-tools в рабочем каталоге; основание показано в скобках): JCRE проверяет каждый импорт по своим пакетам при верификации load-файла (JC VM spec 4.5.2), поэтому карта со старой платформой отклоняет LOAD сразу после завершения компонента Import (в строке шага виден remote SW карты и компонент, который завершил блок, например завершает Import) — меньший размер блока LOAD помогает только если блок превышает буфер карты. Повреждённый файл не проходит анализ, и Execute остаётся недоступной до успешного анализа. Оценка носит справочный характер и никогда не используется при установке: NVRAM — это образ кода плюс постоянные данные (сумма в стиле C6+C8 по GP Table 11-48; управление памятью карты, округление и запись в реестре не учитываются, а образ статических полей входит в обе части); рекомендуемый C6 следует за load-файлом, а рекомендуемые квоты — это десятичные значения в байтах (шестнадцатеричное значение в скобках — это кодирование параметров установки C7/C8: C6 — энергонезависимый код, C7 — энергозависимая RAM, C8 — энергонезависимые данные).
Проверка совместимости CAPОтвечает на один вопрос, ничего не устанавливая: может ли эта карта удовлетворить список импортов CAP? Сервер выполняет проверку формата, INSTALL[for load] и блоки LOAD только до блока, завершающего компонент Import — именно там JCRE проверяет список импортов — и останавливается: без флага последнего блока и без INSTALL[for install], поэтому ничего не фиксируется и удалять после этого ничего не нужно. Список «Версии импортов для проверки» предзаполняется из анализа CAP: AID=version переписывает запрошенную версию импорта, +AID=version добавляет синтетический импорт (запрос возможностей карты: LOAD проходит, только если карта экспортирует этот пакет версии ≥ указанной), а «Все 0.0» снимает все требования. Использование в два шага: нажмите «Все 0.0» — если проверка проходит, причиной был список импортов; затем возвращайте по одной строке, чтобы найти пакет и версию, которых не хватает карте. Вердикт явно указывает, что проверяется только этап LOAD/импортов: этап связывания (наличие реально вызываемых API) проверяет настоящая установка на INSTALL[for install]. Запуск и результат показываются в окне (как при установке): строки шагов, вердикт и блок деталей по каждому AID — при отказе он называет раздел CAP, который покрывает неудачный блок (диапазон байтов load-файла и компоненты, и завершает ли этот блок Import), SW команды с его значением, список импортов ровно в том виде, в каком он проверялся (версии CAP, правки пробы, добавленные синтетические записи, строки без совпадений) и что означает состояние пробы; при успехе — список импортов, который прошёл проверку.
- + diff --git a/frontend/index.html b/frontend/index.html index e6e39e8..8c58d51 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1877,7 +1877,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.13.2'; +const SIMPLE_VERSION = '3.13.3'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== @@ -9937,10 +9937,15 @@ async function ramExplore(sp) { // occurrences (re-issued as a next-occurrence page, P2.b1=1). Returns // {form, parsed, sw, entries, fmt}; form is null when no form produced // data. - async function expandedPass(p1, label, parser, spi2) { - let forms = expandedForm - ? [expandedForm].concat(RAM_EXPANDED_FORMS.filter(f => f !== expandedForm)) - : RAM_EXPANDED_FORMS.slice(); + // The forms each P1 value actually attempted, so the version retry can + // skip the combinations the main pass already refused (the form memory can + // skip the tag-list forms for P1=10 - see elfTagListRetry). + const expandedTried = {}; + async function expandedPass(p1, label, parser, spi2, onlyForms) { + let forms = onlyForms ? onlyForms.slice() + : expandedForm + ? [expandedForm].concat(RAM_EXPANDED_FORMS.filter(f => f !== expandedForm)) + : RAM_EXPANDED_FORMS.slice(); if (p1 === '80') forms = [expandedForm || 'tags']; const runFmt = sp.ram_format || 'auto'; let lastSw = ''; @@ -9948,6 +9953,8 @@ async function ramExplore(sp) { let stopPass = false; for (const form of forms) { if (cntrLow || stopPass) break; + if (!expandedTried[p1]) expandedTried[p1] = []; + if (expandedTried[p1].indexOf(form) < 0) expandedTried[p1].push(form); const fmts = ramFmtLadder(runFmt, expandedFmt); for (const fmt of fmts) { if (cntrLow) break; @@ -10190,23 +10197,37 @@ async function ramExplore(sp) { if (!elfs.length) errors.push(...elfFailure); } // The ELF tag-list forms may be refused for P1=10 while P1=20 accepts - // them (a card's P1=10 implementation can be stricter): when the ELF rows - // have no version and the details pass never got a tag-list form, walk the - // ladder once more with P1=20 and merge by AID (v3.7.0). + // them (a card's P1=10 implementation can be stricter), and the form + // memory can skip the tag-list forms for P1=10 altogether when the Apps + // listing served only the no-tag-list form (live 2026-10-02: EP BAP - its + // P1=10 tag lists were never queried while P1=20 refused them). When the + // ELF rows have no version and the details pass never got a tag-list form, + // walk the untried tag-list forms for both P1 values and merge by AID + // (v3.7.0); only the tag-list forms are retried - the no-tag-list form + // never carries CE - and the walk stops as soon as a version appears. async function elfTagListRetry() { if (cntrLow || !elfs.length || elfs.some(o => o.version)) return; const g = ramExpandedGroups(expandedReport).find(x => x.label === t('ELFs')); if (g && ['tags', 'noea', 'lean'].indexOf(g.form) >= 0) return; const saved = expandedForm; - expandedForm = null; // start the ladder at the richest form - try { - const out = await expandedPass('20', t('ELFs'), ramParseElfStatus, ramListingSpi2('20')); - expandedReport.push({ p1: '20', label: t('ELFs'), compact: true, - form: out.form, sw: out.sw, entries: out.entries, - transport: out.transport, more: out.more }); - if (out.parsed.length) ramMergeExpanded(elfs, out.parsed); - } finally { - if (expandedForm === null) expandedForm = saved; + for (const p1 of ['10', '20']) { + const tried = expandedTried[p1] || []; + const forms = ['tags', 'noea', 'lean'].filter(f => tried.indexOf(f) < 0); + if (!forms.length) continue; + expandedForm = null; // start the ladder at the richest form + try { + const out = await expandedPass(p1, t('ELFs'), ramParseElfStatus, + ramListingSpi2(p1), forms); + if (out.form) { + expandedReport.push({ p1: p1, label: t('ELFs'), compact: true, + form: out.form, sw: out.sw, entries: out.entries, + transport: out.transport, more: out.more, fmt: out.fmt }); + if (out.parsed.length) ramMergeExpanded(elfs, out.parsed); + } + } finally { + if (expandedForm === null) expandedForm = saved; + } + if (elfs.some(o => o.version)) break; } } await elfTagListRetry(); @@ -10260,11 +10281,19 @@ async function ramExplore(sp) { // holds the data), so it is retried with the GET RESPONSE chained into the // compact command string, like the ladder (v3.7.1). async function elfPerAidVersions() { - const missing = elfs.filter(e => !e.version && e.aid); - for (const e of missing.slice(0, 6)) { - if (cntrLow) break; + // Budget the queries, not the list positions: a package without an + // applet instance has nothing to ask for, and counting it would starve + // the packages that do (live 2026-10-02: EP BAP's first six packages + // are Oracle/JC-API entries with no app instances - the A113 packages + // were never queried). + const missing = []; + elfs.forEach(e => { + if (e.version || !e.aid) return; const app = ramElfAppletCandidate(e, apps); - if (!app) continue; + if (app) missing.push({ elf: e, app: app }); + }); + for (const { elf: e, app } of missing.slice(0, 6)) { + if (cntrLow) break; ramShowProgress(t('Version') + ' ' + e.aid + '...'); const apdu = ramExpandedQueryApdu('40', 'tags', false, app.aid); const send = async a => { diff --git a/frontend/sw.js b/frontend/sw.js index e2d7f8b..828e032 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v372'; +const CACHE = 'simple-v373'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/ram.test.js b/frontend/tests/ram.test.js index cde793d..b4abe18 100644 --- a/frontend/tests/ram.test.js +++ b/frontend/tests/ram.test.js @@ -939,10 +939,12 @@ test('ramStepLine names a bad-format failure instead of "PoR ok"', () => { assert.ok(plain.includes('9000') || plain.includes('PoR ok'), plain); }); -test('the Explore retries the ELF tag lists with P1=20 and per applet AID', () => { +test('the Explore retries the ELF tag lists for both P1 values and per applet AID', () => { const src = extractFunc(html, 'ramExplore'); - assert.ok(src.includes('elfTagListRetry'), 'the P1=20 tag-list retry must exist'); - assert.ok(src.includes("expandedPass('20'"), 'the retry must query P1=20'); + assert.ok(src.includes('elfTagListRetry'), 'the tag-list retry must exist'); + assert.ok(src.includes("for (const p1 of ['10', '20'])"), 'the retry must walk both P1 values'); + assert.ok(src.includes('expandedTried[p1]'), 'already-tried forms must be skipped'); + assert.ok(src.includes("['tags', 'noea', 'lean'].filter"), 'only the tag-list forms are retried'); assert.ok(src.includes('elfPerAidVersions'), 'the per-AID version queries must exist'); assert.ok(src.includes("ramExpandedQueryApdu('40', 'tags', false, app.aid)"), 'the per-AID query must use the AID filter'); @@ -950,6 +952,10 @@ test('the Explore retries the ELF tag lists with P1=20 and per applet AID', () = 'the module->applet mapping must be used'); assert.ok(src.includes('ramChainGetResponse(apdu)'), 'the single-AID 61xx answer must be fetched with a chained GET RESPONSE'); + // the per-AID budget counts queries, not list positions (a package with + // no applet instance is skipped before the cap) + assert.ok(src.includes('if (app) missing.push({ elf: e, app: app });'), + 'the per-AID candidates must be filtered before the cap'); }); test('every SCP80/RAM flow checks the low counter and stops', () => { diff --git a/pyproject.toml b/pyproject.toml index ad36f7c..4c8b984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.13.2" +version = "3.13.3" 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 79b1f64..68cff38 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -32,7 +32,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.13.2' +VERSION = '3.13.3' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
OperationDescription
Explore Card (all GP data)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). The compact listing carries the AID, life cycle and privileges (the legacy format has no version); if a card rejects it (P2.b2=0, deprecated by GP) the queries are retried in the expanded E3 form, and the Fetch expanded registry details (load-file / SD associations) checkbox also requests it as an extra pass and merges the expanded-only fields: the Executable Load File version, Load File AID, Security Domain AID, module lists, the applet STK menu entries (SCP Registry Data, TS 102 226 8.2.1.6) and any other registry data objects the card returns (shown raw, never dropped) — it costs a few extra secured packets per Explore and is on by default (remembered per browser; untick to skip). Cards differ in what they accept, so the expanded query is tried in three forms — the reference tag list (with the TS 102 226 EA), the same list without EA, and no tag list at all (the GlobalPlatformPro form) — and the explorer reports which form worked and where the run fell back to the compact listing (package versions are then unavailable). Results appear in an explorer view (the success line stays minimal; the counts are in the section headings) with per-item Delete buttons and the detected RAM command format (hover for what compact vs expanded means).
Explore Card (all GP data)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). The compact listing carries the AID, life cycle and privileges (the legacy format has no version); if a card rejects it (P2.b2=0, deprecated by GP) the queries are retried in the expanded E3 form, and the Fetch expanded registry details (load-file / SD associations) checkbox also requests it as an extra pass and merges the expanded-only fields: the Executable Load File version, Load File AID, Security Domain AID, module lists, the applet STK menu entries (SCP Registry Data, TS 102 226 8.2.1.6) and any other registry data objects the card returns (shown raw, never dropped) — it costs a few extra secured packets per Explore and is on by default (remembered per browser; untick to skip). Cards differ in what they accept, so the expanded query is tried in three forms — the reference tag list (with the TS 102 226 EA), the same list without EA, and no tag list at all (the GlobalPlatformPro form) — and the explorer reports which form worked and where the run fell back to the compact listing (package versions are then unavailable). When the ELF rows still carry no version, the tag-list forms are retried for both P1=10 and P1=20, skipping the combinations already refused, and the walk stops as soon as a version appears. Results appear in an explorer view (the success line stays minimal; the counts are in the section headings) with per-item Delete buttons and the detected RAM command format (hover for what compact vs expanded means).
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: library name, AID and the minimum version ≥ x.y, 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) — grouped under the Package, Requires, Memory and Components headings. The Platform line names the minimum Java Card level the card must provide (derived from the javacard.framework import; the header int flag additionally requires int support, Java Card 2.2+) and the ETSI release the UICC API imports come from (uicc.toolkit version → REL-N, per the workspace sim-tools corpus; shown with its basis in brackets): the JCRE checks every import against its own packages when it verifies the load file (JC VM spec 4.5.2), so a card whose platform is older rejects the LOAD as soon as the Import component is complete (the step line carries the card's remote SW and names the component the block completed, e.g. completes Import) — a smaller LOAD block only helps when the block exceeds the card's buffer. A corrupt or wrong-format file fails the analysis, and Execute 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 — card memory management, rounding and the registry entry are not included, and the static field image is counted in both parts); the suggested C6 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).
CAP compatibility testAnswers one question without installing anything: can this card satisfy the CAP's import list? The server runs the format check, INSTALL[for load] and the LOAD blocks only up to the block that completes the Import component — the point where the JCRE verifies the import list — then stops: no last-block flag and no INSTALL[for install], so nothing is committed and nothing has to be deleted afterwards. The Import versions to test list is pre-filled from the CAP analysis: AID=version rewrites an import's requested version, +AID=version appends a synthetic import (a card-capability query: the LOAD passes only if the card exports that package at ≥ that version), and Set all 0.0 drops every requirement. Two-step use: press Set all 0.0 — if the test passes, the import list was the blocker; then restore one line at a time to find the package and version the card lacks. The verdict states it covers the LOAD/import gate only: the link gate (whether the applet's actual API calls exist) is what a real install checks at INSTALL[for install]. The run and its result appear in a popup (like the install): the step lines, the verdict and a per-AID detail block — on a rejection it names the CAP section the failing block covers (load-file byte range and components, and whether that block completes Import), the remote status word with its meaning, the import list exactly as tested (CAP versions, probe rewrites, appended synthetic entries, unmatched lines) and what the probe state implies; on success it lists the import list that passed.