v3.19.2: the three-status-words disambiguation (envelope SW vs por.sw)

The script author read por.sw as the envelope SW, so a por.sw expectation
on an applet's own TAR failed with (none).

- docs/test-scripts.md: a 'The three status words (envelope SW / PoR
  status / R-APDU SW)' table in the authoring section - where each one
  comes from, the script field and the report row - plus a cross-reference
  from the por check paragraph; help EN/RU 8.9 points at it.
- a failed por.sw check on a response_type: 'raw' decode carries the check
  detail 'the response is application data - an applet's own TAR has no
  R-APDU SW; assert it with data' (the PWA renders check details).
- the runner's log prints APP DATA=<hex> (no R-APDU SW) for application
  responses instead of the misleading R-APDU SW=(none) data=...; the
  compact/scripting lines are unchanged.
- the step editor's custom-PoR block gets a one-line hint about the same.
Tests: 821 frontend + 730 Python; sw cache simple-v391.
This commit is contained in:
2026-10-04 22:19:58 +03:00
parent 14c43f37b3
commit 78bc91d297
9 changed files with 88 additions and 22 deletions
+1 -1
View File
@@ -564,7 +564,7 @@
<p class="text-sm mb-2"><strong>Тест-скрипт</strong> &mdash; упорядоченный список <strong>действий</strong> и <strong>ожиданий</strong> проактивных команд, выполняемый на подключённой карте на стороне сервера. Пока запуск активен, карта принадлежит скрипту: остальные операции с картой отклоняются (HTTP&nbsp;409), фоновый опрос STATUS приостанавливается; отчёт о запуске хранится в памяти (в том числе после перезагрузки страницы) до нажатия <strong>Очистить отчёт</strong>. Скрипты хранятся в <strong>хранилище на сервере</strong> (<code class="font-mono text-sm">~/.pysim-simple-server/test_scripts.json</code>, <code class="font-mono text-sm">--test-scripts</code>), поэтому все браузеры работают с одним набором; изменения записываются в хранилище по мере правки (кнопки &laquo;Сохранить&raquo; нет &mdash; черновик без валидного шага остаётся локальным); экспорт/импорт JSON остаётся для резервных копий и переноса скриптов между стендами (старый экспорт из localStorage импортируется как есть).</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li><strong>Действия</strong> &mdash; <em>Envelope</em> (Event Download: сырой тип события + данные, с опциональным переопределением источника &mdash; 82 терминал / 83 сеть) или <em>Event</em> (семантические формы: Location status, Access technology change, Network rejection, Data connection status change &mdash; поля редактируются как объект JSON в редакторе шагов, а для остальных событий форма события на вкладке «Телефон» показывает готовый hex), <em>Menu Selection</em> (item id 1&ndash;255 или текст пункта с точным/частичным совпадением, по умолчанию без учёта регистра &mdash; выбор <em>«Учёт регистра»</em> в редакторе шагов включает точное совпадение; текст подбирается по меню карты, которое следует за параметрами установки апплета), <em>UPDATE/READ файла</em> (путь + режим; чтение проверяет данные файла), <em>сырой APDU</em> (без авто-обработчика), <em>SCP80</em> (C-APDU или готовый защищённый пакет, опциональные переопределения номера keyset (kvn) / TAR / SPI1 / SPI2 и формат команды &mdash; компактный или обёрнутый в шаблон Command Scripting TS 102 226 &sect;5.2.1 <em>expanded</em> (AA) / <em>expanded AE&nbsp;80</em>; ключи и счётчик берутся из пресета карты, подобранного по ICCID подключённой карты &mdash; запуск называет пресет в хранилище, поэтому всегда используются его keysets, TAR и счётчик &mdash; а счётчик записывается обратно после запуска) и <em>STATUS</em> (при <em>попытках</em> &gt; 1 опрашивает, пока карта не объявит команду). У каждого действия есть проверка ответа: ожидаемые SW и данные (точно или маска с подстановкой <code class="font-mono text-sm">?</code> &mdash; <code class="font-mono text-sm">91??</code>) и проверка PoR для SCP80 (<em>любой / без PoR / por_ok</em> или своя проверка декодированного PoR &mdash; статус / SW / данные; транспорт через SEND SHORT MESSAGE работает так же).</li>
<li><strong>Ожидания</strong> &mdash; карта должна объявить проактивную команду на предыдущем шаге (SW <code class="font-mono text-sm">91XX</code>, TS 102 221 &sect;7.4.2.1 / TS 102 223 &sect;6.3); <code class="font-mono text-sm">9000</code> означает &laquo;команды нет&raquo; и считается ошибкой &mdash; добавьте действие STATUS, если апплет выдаёт команду при опросе. Проверяются тип команды, квалификатор и содержимое: текст / пункт / raw, проверка <em>Alpha</em> (идентификатор Alpha команды, тег <code class="font-mono text-sm">05</code>/<code class="font-mono text-sm">85</code> &mdash; отличается от текстовой строки <code class="font-mono text-sm">8D</code>), проверка <em>SMS TPDU</em> (submit-TPDU команды SEND SHORT MESSAGE: получатель / PID / DCS / UDL / данные &mdash; для собственного SMS апплета), проверка <em>PoR</em> (SEND SHORT MESSAGE несёт PoR, когда пакет запросил PoR-in-submit, бит SPI2 <code class="font-mono text-sm">20</code>: статус ответа, SW R-APDU и данные ответа; перед этим должен выполниться шаг SCP80) и проверка <em>Files</em> (File List команды REFRESH, например квалификатор <code class="font-mono text-sm">00</code> = NAA initialization with full file change notification). <strong>TERMINAL RESPONSE</strong> (результат, идентификатор пункта, текст, доп. TLV) является частью инструкции.</li>
<li><strong>Ожидания</strong> &mdash; карта должна объявить проактивную команду на предыдущем шаге (SW <code class="font-mono text-sm">91XX</code>, TS 102 221 &sect;7.4.2.1 / TS 102 223 &sect;6.3); <code class="font-mono text-sm">9000</code> означает &laquo;команды нет&raquo; и считается ошибкой &mdash; добавьте действие STATUS, если апплет выдаёт команду при опросе. Проверяются тип команды, квалификатор и содержимое: текст / пункт / raw, проверка <em>Alpha</em> (идентификатор Alpha команды, тег <code class="font-mono text-sm">05</code>/<code class="font-mono text-sm">85</code> &mdash; отличается от текстовой строки <code class="font-mono text-sm">8D</code>), проверка <em>SMS TPDU</em> (submit-TPDU команды SEND SHORT MESSAGE: получатель / PID / DCS / UDL / данные &mdash; для собственного SMS апплета), проверка <em>PoR</em> (SEND SHORT MESSAGE несёт PoR, когда пакет запросил PoR-in-submit, бит SPI2 <code class="font-mono text-sm">20</code>: статус ответа, SW R-APDU и данные ответа; перед этим должен выполниться шаг SCP80 &mdash; SW R-APDU находится <em>внутри</em> полезной нагрузки ответа: собственный TAR апплета возвращает данные приложения, поэтому проверяйте данные, а SW оставляйте пустым) и проверка <em>Files</em> (File List команды REFRESH, например квалификатор <code class="font-mono text-sm">00</code> = NAA initialization with full file change notification). <strong>TERMINAL RESPONSE</strong> (результат, идентификатор пункта, текст, доп. TLV) является частью инструкции.</li>
<li><strong>Уровни ошибок</strong> &mdash; несовпадение помечает шаг как <em>Ошибка</em> (скрипт останавливается) или <em>Предупреждение</em> (продолжает); каждая проверка показывается с ожидаемым и фактическим значением.</li>
<li><strong>Шаблоны</strong> &mdash; Empty, <em>Обзор меню STK</em>, <em>Апплет через SCP80</em>; редактор шагов — формный (поля действия Event вводятся объектом JSON), отдельные скрипты экспортируются/импортируются как JSON.</li>
</ul>
+1 -1
View File
@@ -563,7 +563,7 @@
<p class="text-sm mb-2">A <strong>test script</strong> is an ordered list of <strong>actions</strong> and proactive-command <strong>expectations</strong> executed server-side on the equipped card. While a run is active the card belongs to the script: other card actions are refused (HTTP&nbsp;409) and background STATUS polling is paused; the run report is kept in memory (also after reloading the page) until <strong>Clear report</strong>. Scripts live in the <strong>server-side store</strong> (<code class="font-mono text-sm">~/.pysim-simple-server/test_scripts.json</code>, <code class="font-mono text-sm">--test-scripts</code>), so every browser manages the same set; edits are written to the store as you change them (there is no Save button &mdash; a draft without a valid step stays local until it is valid); JSON export/import remains for backup and for moving scripts between benches (the old localStorage export imports as-is).</p>
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
<li><strong>Actions</strong> &mdash; <em>Envelope</em> (Event Download: raw event type + data, with an optional source override &mdash; 82 terminal / 83 network) or <em>Event</em> (the semantic forms: Location status, Access technology change, Network rejection, Data connection status change &mdash; the fields are edited as a JSON object in the step editor, and the Phone tab's event form shows the built hex for the other events), <em>Menu Selection</em> (item id 1&ndash;255, or the item's text with an exact/contains match, case-insensitive by default &mdash; the step editor's <em>Match case</em> choice switches to an exact-case match; the text is resolved against the card's menu, which follows the applet's install parameters), <em>UPDATE/READ file</em> (path + mode; reads verify the file data), <em>raw APDU</em> (no auto-handler), <em>SCP80</em> (C-APDU or a pre-built secured packet, optional keyset number (kvn) / TAR / SPI1 / SPI2 overrides and a command format &mdash; compact, or the TS 102 226 &sect;5.2.1 Command Scripting template <em>expanded</em> (AA) / <em>expanded AE&nbsp;80</em> around the C-APDU; the keys and the counter come from the card preset matched by the equipped card's ICCID &mdash; the run names the stored preset, so it always uses its keysets, TARs and counter &mdash; and the counter is written back to it after the run) and <em>STATUS</em> (with <em>attempts</em> &gt; 1 it polls until the card announces a command). Every action has a response check: expected SW and data (exact, or a mask with <code class="font-mono text-sm">?</code> nibble wildcards &mdash; <code class="font-mono text-sm">91??</code>) plus a PoR check for SCP80 (<em>any / no PoR / por_ok</em>, or a custom assertion of the decoded PoR &mdash; status / SW / data; the SEND SHORT MESSAGE transport works the same).</li>
<li><strong>Expectations</strong> &mdash; the card must have announced a proactive command in the previous step (SW <code class="font-mono text-sm">91XX</code>, TS 102 221 &sect;7.4.2.1 / TS 102 223 &sect;6.3); a <code class="font-mono text-sm">9000</code> means &ldquo;no command&rdquo; and is an error &mdash; add a STATUS action when the applet delivers on poll. The command type, qualifier and contents are verified: text / item / raw checks, an <em>Alpha</em> check (the command's Alpha identifier, tag <code class="font-mono text-sm">05</code>/<code class="font-mono text-sm">85</code> &mdash; distinct from the <code class="font-mono text-sm">8D</code> text string), an <em>SMS TPDU</em> check (the submit TPDU of a SEND SHORT MESSAGE: destination / PID / DCS / UDL / user data &mdash; for an applet's own SMS), a <em>PoR</em> check (a SEND SHORT MESSAGE carries the PoR when the packet requested PoR-in-submit, SPI2 bit <code class="font-mono text-sm">20</code>: the response status, the R-APDU SW and the response data; make sure an SCP80 step ran first) and a <em>Files</em> check (the File List of a REFRESH, e.g. qualifier <code class="font-mono text-sm">00</code> = NAA initialization with full file change notification). The scripted <strong>TERMINAL RESPONSE</strong> (result, item identifier, text, extra TLVs) is part of the instruction.</li>
<li><strong>Expectations</strong> &mdash; the card must have announced a proactive command in the previous step (SW <code class="font-mono text-sm">91XX</code>, TS 102 221 &sect;7.4.2.1 / TS 102 223 &sect;6.3); a <code class="font-mono text-sm">9000</code> means &ldquo;no command&rdquo; and is an error &mdash; add a STATUS action when the applet delivers on poll. The command type, qualifier and contents are verified: text / item / raw checks, an <em>Alpha</em> check (the command's Alpha identifier, tag <code class="font-mono text-sm">05</code>/<code class="font-mono text-sm">85</code> &mdash; distinct from the <code class="font-mono text-sm">8D</code> text string), an <em>SMS TPDU</em> check (the submit TPDU of a SEND SHORT MESSAGE: destination / PID / DCS / UDL / user data &mdash; for an applet's own SMS), a <em>PoR</em> check (a SEND SHORT MESSAGE carries the PoR when the packet requested PoR-in-submit, SPI2 bit <code class="font-mono text-sm">20</code>: the response status, the R-APDU SW and the response data; make sure an SCP80 step ran first &mdash; the R-APDU SW lives <em>inside</em> the response payload: an applet's own TAR returns application data, so check its data and leave the SW empty) and a <em>Files</em> check (the File List of a REFRESH, e.g. qualifier <code class="font-mono text-sm">00</code> = NAA initialization with full file change notification). The scripted <strong>TERMINAL RESPONSE</strong> (result, item identifier, text, extra TLVs) is part of the instruction.</li>
<li><strong>Fail levels</strong> &mdash; a mismatch flags the step as <em>Error</em> (terminates the script) or <em>Warning</em> (continues); each check is reported with expected vs actual values.</li>
<li><strong>Templates</strong> &mdash; Empty, <em>STK menu browsing</em>, <em>Applet via SCP80</em>; the step editor is form-based (the event action's fields are a JSON object) and individual scripts export/import as JSON.</li>
</ul>
+4 -1
View File
@@ -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.19.1';
const SIMPLE_VERSION = '3.19.2';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching =====
@@ -7442,6 +7442,8 @@ function testStepRender() {
html += testFormRow('PoR status', testFormInput('test-f-porstatus', porObj.status, 'por_ok'));
html += testFormRow('PoR SW (hex)', testFormInput('test-f-por-sw', porObj.sw && porObj.sw.value, '6A88'));
html += testFormRow('PoR data (hex)', testFormInput('test-f-por-data', porObj.data && porObj.data.value, 'AA??'));
html += '<p class="mt-1 text-xs text-gray-400 dark:text-slate-500">'
+ esc(t("PoR SW is the R-APDU SW inside a remote-management TAR's response; an applet's own TAR returns application data - assert it with PoR data and leave SW empty.")) + '</p>';
}
}
html += testFormRow('On mismatch', testFormSelect('test-f-fail', step.on_fail || 'error', [
@@ -21194,6 +21196,7 @@ const LANG_RU = {
'PoR status': 'Статус PoR',
'PoR SW (hex)': 'SW PoR (hex)',
'PoR data (hex)': 'Данные PoR (hex)',
"PoR SW is the R-APDU SW inside a remote-management TAR's response; an applet's own TAR returns application data - assert it with PoR data and leave SW empty.": 'SW PoR — это SW R-APDU внутри ответа remote-management TAR; собственный TAR апплета возвращает данные приложения — проверяйте «Данные PoR», оставив SW пустым.',
'PoR check: status, SW or data is required': 'Проверка PoR: нужен status, SW или data',
'Command format': 'Формат команды',
'Compact': 'Компактный',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v390';
const CACHE = 'simple-v391';
const URLS = [
'index.html',
'help.html',
+3
View File
@@ -250,6 +250,9 @@ test('the SCP80 step form renders the format select and the custom PoR fields',
assert.match(body, /<option value="custom" selected>/);
assert.match(body, /id="test-f-porstatus" value="por_ok"/);
assert.match(body, /id="test-f-por-sw" value=""/);
// the block explains where PoR SW comes from (the applet-TAR confusion)
assert.ok(body.includes('R-APDU SW inside a remote-management'), body);
assert.ok(body.includes('leave SW empty'), body);
// the pre-built packet source has no format select (the wrapper applies
// to a C-APDU only)
_testEditStep = { type: 'action', kind: 'scp80', params: { source: 'sp', sp: 'AABB' } };