From 78bc91d2974db132d6aff2824d354feca1a85fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 4 Oct 2026 22:19:58 +0300 Subject: [PATCH] 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= (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. --- docs/test-scripts.md | 24 ++++++++++++++++++- frontend/help-ru.html | 2 +- frontend/help.html | 2 +- frontend/index.html | 5 +++- frontend/sw.js | 2 +- frontend/tests/testscripts.test.js | 3 +++ pyproject.toml | 2 +- pysim_simple_server/server.py | 33 ++++++++++++++++++-------- tests/test_testscript.py | 37 +++++++++++++++++++++++++----- 9 files changed, 88 insertions(+), 22 deletions(-) diff --git a/docs/test-scripts.md b/docs/test-scripts.md index 4e25401..499d453 100644 --- a/docs/test-scripts.md +++ b/docs/test-scripts.md @@ -34,6 +34,26 @@ and the card's preset): | PoR transport + expected PoR | `scp80.check.por`/`check.data` (inline) or `expect SEND SHORT MESSAGE` + `por` check | pick per the SPI2 the applet expects | | Delivery timing | a `status` action when the card delivers on poll | the runner never polls by itself | +### The three status words (envelope SW · PoR status · R-APDU SW) + +An SCP80 step carries three different "SW" values; keep them apart when +writing checks: + +| Where it comes from | What it means | Script field | Report row | +|---|---|---|---| +| the **ENVELOPE** exchange | the card's answer to the command that was sent (`9000` executed, `91XX` a proactive command is pending, `62XX`/`63XX` a warning) | the action's `check.sw` | `SW` | +| the **PoR status** | the SCP80 security-processing verdict inside the response packet (`por_ok`, `cntr_low`, `rc_cc_ds_failed`, `tar_unknown`, …) | `por.status` (or `"por": "ok"`) | `PoR status` | +| the **R-APDU SW** | the response payload's own status word - the last executed command's / GET RESPONSE's status in the compact form (TS 102 226 §5.1.2 Table 5.1), or the last R-APDU's in the scripting (`AB`/`AF`) form | `por.sw` | `PoR SW` | + +`por.sw` is a child of the **response payload**, not of the envelope: it +exists only when the addressed TAR is a **remote-management application** +(ISD / RFM). A third-party applet's **own TAR** answers with +application-defined bytes - the platform passes them through unchanged - so +there is no R-APDU SW to check: `por.sw` reports `(none)` and the whole +secured data is exposed as `por.data`. Assert such a response with `data` +and leave `sw` empty; the step's `check.sw` still asserts the envelope +exchange. + ### Worksheet 1 - RFM update → file change → REFRESH (full FCN) ```json @@ -283,7 +303,9 @@ command-details byte 3 (one byte, exact/mask). SHORT MESSAGE is decoded with that step's keyset, SPI and counter. `status` is the decoded response status name (`por_ok`, `cntr_low`, `rc_cc_ds_failed`, …; `ok` is accepted for `por_ok`); `sw` is the R-APDU -status word and `data` the R-APDU response data (exact/mask) - both come from +status word **inside the response payload** - not the envelope's, see +*The three status words* in the authoring section - and `data` the R-APDU +response data (exact/mask); both come from the decoded response (scripting `AB`/`AF`/compact forms). An applet's **own TAR** answers with its application-defined bytes: when the data cannot be the compact remote response (its command count exceeds the command script that diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 6bbbab5..381b69f 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -564,7 +564,7 @@

Тест-скрипт — упорядоченный список действий и ожиданий проактивных команд, выполняемый на подключённой карте на стороне сервера. Пока запуск активен, карта принадлежит скрипту: остальные операции с картой отклоняются (HTTP 409), фоновый опрос STATUS приостанавливается; отчёт о запуске хранится в памяти (в том числе после перезагрузки страницы) до нажатия Очистить отчёт. Скрипты хранятся в хранилище на сервере (~/.pysim-simple-server/test_scripts.json, --test-scripts), поэтому все браузеры работают с одним набором; изменения записываются в хранилище по мере правки (кнопки «Сохранить» нет — черновик без валидного шага остаётся локальным); экспорт/импорт JSON остаётся для резервных копий и переноса скриптов между стендами (старый экспорт из localStorage импортируется как есть).

diff --git a/frontend/help.html b/frontend/help.html index eef7e0c..9a315ca 100644 --- a/frontend/help.html +++ b/frontend/help.html @@ -563,7 +563,7 @@

A test script is an ordered list of actions and proactive-command expectations executed server-side on the equipped card. While a run is active the card belongs to the script: other card actions are refused (HTTP 409) and background STATUS polling is paused; the run report is kept in memory (also after reloading the page) until Clear report. Scripts live in the server-side store (~/.pysim-simple-server/test_scripts.json, --test-scripts), so every browser manages the same set; edits are written to the store as you change them (there is no Save button — 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).

diff --git a/frontend/index.html b/frontend/index.html index be51cc6..6dd6078 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.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 += '

' + + 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.")) + '

'; } } 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': 'Компактный', diff --git a/frontend/sw.js b/frontend/sw.js index 0ab551a..6616b47 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v390'; +const CACHE = 'simple-v391'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/testscripts.test.js b/frontend/tests/testscripts.test.js index e6c1009..141e103 100644 --- a/frontend/tests/testscripts.test.js +++ b/frontend/tests/testscripts.test.js @@ -250,6 +250,9 @@ test('the SCP80 step form renders the format select and the custom PoR fields', assert.match(body, /