From cb40662e4e026efa26268dd0e91ed41980153d3b 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: Wed, 14 Oct 2026 12:21:16 +0300 Subject: [PATCH] v3.26.0: per-TAR default keysets (the preset/SCP80/RAM rework) Every preset TAR entry carries `keyset` (int 1-15) - the keyset every operation addressed to that TAR uses: mandatory for any non-zero MSL (the counters are keyset-linked), optional for MSL 00 (allowed: some cards reject the no-security 00/00 KIc/KID form). The store fills it automatically when the preset has a single keyset, the validation refuses an entry naming an undefined keyset, and the schema is 6 (a v5 file loads leniently; editing it forces the fix). The keyset selectors are gone: the SCP80 TAR field became a select of the preset's entries (applying the TAR's keyset to the form and seeding the SPI1 from its MSL); the RAM operations use the preset's ISD role entry (the PWA sends only the preset id, the ISD TAR, SPI and the counter override - the server resolves the keyset and the keys: presets.tar_keyset, _preset_tar_keyset, _resolve_operation_keyset, _op_keyset, used by send-ota's APDU path, ram-install, ram-install-app and cap-compat); the counter probe and the test runner's scp80 steps default to their TAR's keyset (an explicit kvn stays an override). A security TAR without a keyset refuses the operation ("set it in the Cards tab") - never a silent keyless send - and the start buttons wait for a usable operation-TAR keyset. The TAR probe checks the preset's TAR list only (the standard TAR_PROBE_TARS list and the custom-TAR localStorage are gone; a `tars` list selects a subset, a TAR outside the preset is refused), each row using its own MSL and default keyset. The Cards editor's TAR table gained a Keyset column (the only keyset preselected, an amber marker when a security MSL lacks one). Docs: help EN/RU, api.md's tar-probe contract, both AGENTS. Tests: the store (the field, the auto-fill, the schema, the string forms), the server resolution (send-ota + the refusal, the probe's per-row keysets and refusals), the PWA (the TAR select, the RAM ISD resolution, the keyset-free requests) - several fixtures updated for the new schema. 891 frontend + 885 Python; sw cache simple-v434. --- docs/api.md | 32 +- frontend/help-ru.html | 12 +- frontend/help.html | 12 +- frontend/index.html | 539 +++++++++++------------- frontend/sw.js | 2 +- frontend/tests/cards_counter.test.js | 31 +- frontend/tests/cards_form.test.js | 115 +++-- frontend/tests/indicator.test.js | 6 + frontend/tests/keysets.test.js | 103 ++--- frontend/tests/ram.test.js | 23 +- frontend/tests/ram_counter_flow.test.js | 10 +- frontend/tests/tar_probe.test.js | 98 ++--- pyproject.toml | 2 +- pysim_simple_server/presets.py | 62 ++- pysim_simple_server/server.py | 315 ++++++++++---- tests/test_fetch_routes.py | 118 ++++-- tests/test_presets.py | 72 +++- tests/test_testscript.py | 14 +- 18 files changed, 955 insertions(+), 611 deletions(-) diff --git a/docs/api.md b/docs/api.md index e6c8897..3bc06bb 100644 --- a/docs/api.md +++ b/docs/api.md @@ -341,28 +341,30 @@ first attempt instead of hammering the card. ### `POST /api/tar-probe` Probe which OTA applications the card has registered: one **secured packet per -TAR** (the standard allocations of TS 101 220 V18.3.0 Annex D, Table D.1 unless -a `tars` list is given), each carrying a harmless C-APDU (`SELECT MF` by -default), with the preset's keyset. Each checked TAR uses **its own MSL** when -the preset carries the entry, otherwise the request's `spi1`; a TAR that -resolves to neither is refused with a hint **before any packet is sent**. The -resolved SPI1 must carry a counter check (b5b4 ≠ 00) and ask for a PoR. -Every accepted packet consumes a counter, which is persisted into the keyset -like any other operation, so the probe is refused when the SPI1 has no counter -check or the named preset does not define the keyset number. A missing -`cntr` is seeded from that keyset (v3.20.0; an explicit value wins but never -lowers the stored counter). The delivered SMS carries the OTA CPI IE by +TAR of the named preset** (v3.26.0 — the probe checks the preset's TAR list +only; a `tars` list may select a subset, and a TAR outside the preset is +refused with `400`), each carrying a harmless C-APDU (`SELECT MF` by default). +Each row uses **its own MSL** (the request's `spi1` is only a fallback for a +TAR the preset does not carry) and **its own default keyset** — the keyset +resolved from that TAR's entry, so the request does not carry KIc/KID/keys and +the probe's keyset selector is gone. A TAR whose MSL requires security +(non-zero) without a default keyset, or one naming an undefined keyset, is +refused with a hint **before any packet is sent**. The resolved SPI1 must +carry a counter check (b5b4 ≠ 00) and ask for a PoR. Every accepted packet +consumes a counter, which is persisted into that row's keyset like any other +operation (each keyset has its own counter, TS 102 225 A.1); an explicit +`cntr` seeds the first row only. The delivered SMS carries the OTA CPI IE by default (`includeCpi`, as in `/api/send-ota`). ```json -{"preset_id": "…", "kic": "25", "kid": "25", "kicKey": "…", "kidKey": "…", - "cntr": "0000000187", "apdu": "00A40000023F00", "tars": ["000000", "B00000"]} +{"preset_id": "…", "spi1": "16", "apdu": "00A40000023F00", + "tars": ["000000", "B00000"]} ``` **Response:** `{"success": true, "registered": N, "total": M, "final_cntr": "…", "kvn": 2, "spi1": "16", "spi2": "01", - "results": [{"tar": "000000", "label": "Issuer Security Domain (compact)", - "spi1": "16", "verdict": "registered", "sw": "9000", + "results": [{"tar": "000000", "label": "ISD", "spi1": "16", "kvn": 2, + "verdict": "registered", "sw": "9000", "por_status": "por_ok", "por_sw": "6D00", "por_data": ""}], "steps": [...]}` diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 3097ef1..85e9b75 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -283,7 +283,7 @@
Верхнеуровневая вкладка SCP80 объединяет разделы, связанные с SCP80. Переключение — тремя переключателями: Secured Packet, RAM и TAR-сканер. Собирает защищённые пакеты SCP80 по ETSI TS 102 225. При подключении карты сервер читает её EF.ICCID (2FE2); если в пресете указан тот же номер (цифрами или сырым hex из EF), он автоматически выбирается во всех трёх представлениях SCP80. Вердикт низкого счётчика (cntr_low или предупреждающий ответ ENVELOPE, в котором всё ещё есть такой PoR) не исправляется повторной отправкой: любая операция SCP80/RAM немедленно останавливается. Карта не сообщает свой счётчик (поле CNTR в PoR — копия счётчика пакета), поэтому в уведомлении есть ограниченный пробник «Синхронизировать счётчик» — read-only команда с возрастающими значениями счётчика (шаг удваивается, потолок 32 бита, не более 40 пакетов), пока карта не примет одно; затем сохраняется принятое+1 (поиск, остановленный вердиктом безопасности после отказа по счётчику, тоже сохраняет значение — с янтарным предупреждением проверить настройки безопасности) — и кнопка К пресету для ручного повышения; больше ничего автоматически не записывается.
Собирает защищённые пакеты SCP80 по ETSI TS 102 225. Селектор Keyset рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик; отправка отклоняется, если в пресете нет такого номера keyset или KIc/KID несут разные номера (TS 102 225 A.2). Селектор недоступен, пока не выбран пресет карты. Кнопка запуска недоступна, пока не выбран пресет карты и один из его keysets. Поле TAR предлагает записи TAR пресета (с описанием и MSL); при применении пресета TAR операции подставляется, а SPI1 берётся из его MSL (минимальный SPI1, TS 102 226 §8.2.1.3.2.4 — карта проверяет его до обработки защиты и отвечает 0A, если SPI1 пакета ниже). Значение ниже MSL показывает жёлтое предупреждение под формой; введённый вручную TAR сохраняет SPI1 оператора. Поле Счётчик — это переопределение: оно пустое, а следующий счётчик пресета показывается подсказкой (placeholder) — пустое поле позволяет серверу взять счётчик из хранилища и сохранить принятое картой значение; введённое значение используется только для этой отправки и никогда не записывается в пресет, а меньшее значение не откатывает сохранённый счётчик (сохраняются только движения вперёд).
Собирает защищённые пакеты SCP80 по ETSI TS 102 225. Селектор TAR перечисляет записи TAR выбранного пресета (метка роли или описание, MSL и keyset по умолчанию); выбор записи применяет её keyset по умолчанию к форме (её KIc/KID и ключи — v3.26.0: селектор keyset убран, его определяет TAR) и подставляет SPI1 из её MSL (минимальный SPI1, TS 102 226 §8.2.1.3.2.4 — карта проверяет его до обработки защиты и отвечает 0A, если SPI1 пакета ниже). Значение ниже MSL показывает жёлтое предупреждение под формой. Кнопка запуска недоступна, пока не выбран пресет карты и keyset TAR операции не пригоден: TAR, чей MSL требует защиты (любой ненулевой MSL — счётчики привязаны к keyset), обязан иметь keyset по умолчанию в пресете, а TAR с MSL 00 может идти без ключей или всё же нести их (некоторые карты отвергают форму 00/00 без защиты). Отправка отклоняется, когда в пресете нет указанного номера keyset или KIc/KID несут разные номера (TS 102 225 A.2). Поле Счётчик — это переопределение: оно пустое, а следующий счётчик пресета показывается подсказкой (placeholder) — пустое поле позволяет серверу взять счётчик из хранилища и сохранить принятое картой значение; введённое значение используется только для этой отправки и никогда не записывается в пресет, а меньшее значение не откатывает сохранённый счётчик (сохраняются только движения вперёд).
| Поле | Размер | Описание |
|---|
| Поле | Описание |
|---|
| Field | Size | Description |
|---|
| Field | Description | |||
|---|---|---|---|---|
| TAR | MSL | +Keyset | Description | ';
+ html += '';
+ if (warn) {
+ html += '\u26a0';
+ }
+ html += ' | ';
+ return html;
+}
+
function cardsTarRowHtml(e, i) {
const cls = 'border border-gray-300 dark:border-slate-600 text-sm rounded px-2 py-1.5 dark:bg-slate-800 font-mono';
const role = CARDS_TAR_ROLES[i] || '';
let cells = '' + - ' | '; + ' | ' + + cardsTarKeysetCell(e, i); if (role) { cells += ' | ' + esc(CARDS_TAR_ROLE_DESC[role] || '') + ' | ' + ''; @@ -13673,7 +13653,7 @@ function cardsTarRowsRender(entries) { const list = entries || []; const roles = CARDS_TAR_ROLES.map(role => { const e = list.find(x => x.role === role) || {}; - return { tar: e.tar || '', msl: e.msl || '' }; + return { tar: e.tar || '', msl: e.msl || '', keyset: e.keyset || null }; }); const free = list.filter(e => !e.role); _cardsTarCount = free.length; @@ -13682,7 +13662,7 @@ function cardsTarRowsRender(entries) { } function cardsTarAdd() { - cardsTarRowsRender(cardsTarsFromTable().concat([{ tar: '', msl: '', desc: '' }])); + cardsTarRowsRender(cardsTarsFromTable().concat([{ tar: '', msl: '', desc: '', keyset: null }])); } // `i` is the table row index; the role rows are fixed and never removed. @@ -13703,13 +13683,21 @@ function cardsFreeTarsFromForm() { return el ? String(el.value).trim() : ''; }; const e = { tar: get('tar').replace(/\s/g, '').toUpperCase(), - msl: get('msl').replace(/\s/g, '').toUpperCase(), desc: get('desc') }; - if (!e.tar && !e.msl && !e.desc) continue; + msl: get('msl').replace(/\s/g, '').toUpperCase(), desc: get('desc'), + keyset: cardsTarKeysetFromForm(idx) }; + if (!e.tar && !e.msl && !e.desc && !e.keyset) continue; out.push(e); } return out; } +// The keyset select of a TAR row as a number (null for none). +function cardsTarKeysetFromForm(idx) { + const el = document.getElementById('cards-tr-' + idx + '-keyset'); + const v = el && el.value ? parseInt(el.value, 10) : NaN; + return (Number.isInteger(v) && v > 0) ? v : null; +} + // The TAR table exactly as displayed: the role rows as typed (an emptied field // stays empty) plus the free rows - the re-render source for Add/Remove, so an // emptied field is not silently refilled with its default. @@ -13721,7 +13709,8 @@ function cardsTarsFromTable() { }; return { role: role, tar: get('tar').replace(/\s/g, '').toUpperCase(), - msl: get('msl').replace(/\s/g, '').toUpperCase(), desc: '' }; + msl: get('msl').replace(/\s/g, '').toUpperCase(), desc: '', + keyset: cardsTarKeysetFromForm(i) }; }); return out.concat(cardsFreeTarsFromForm()); } @@ -13732,45 +13721,24 @@ function cardsTarsFromTable() { function cardsTarsFromForm() { return cardsTarsFromTable().map(e => e.role ? { role: e.role, tar: e.tar || cardsRoleDefault(e.role), - msl: e.msl || CARDS_MSL_DEFAULT, desc: '' } + msl: e.msl || CARDS_MSL_DEFAULT, desc: '', keyset: e.keyset || null } : e); } -// The role rows back to their pre-settled defaults (TAR + MSL 16) and no free -// rows. +// The role rows back to their pre-settled defaults (TAR + MSL 16, no keyset) +// and no free rows. function cardsTarsReset() { cardsTarRowsRender(CARDS_TAR_ROLES.map(role => ({ - role: role, tar: cardsRoleDefault(role), msl: CARDS_MSL_DEFAULT, desc: '' }))); + role: role, tar: cardsRoleDefault(role), msl: CARDS_MSL_DEFAULT, desc: '', + keyset: null }))); } -// ===== Keyset selectors (SCP80 / RAM) ===== -// The selector follows the KIc/KID bytes: a keyset of the selected preset that -// matches their keyset number is selected; a number the preset does not define -// shows as "not defined" (and the packet actions refuse it). +// ===== Keyset resolution (SCP80 / RAM / TAR probe) ===== +// v3.26.0: the keyset selectors are gone - each TAR carries its default keyset +// in the preset, the SCP80 TAR select applies it to the form and the RAM +// operations use the ISD role entry (the server resolves the same rule). Only +// the start buttons' availability refresh remains here. function spKeysetSync() { - const kic = (document.getElementById('sp-kic-hex') || {}).value; - const kid = (document.getElementById('sp-kid-hex') || {}).value; - // A keyset only exists inside a card preset: with no preset selected the - // selector is disabled and cleared, so a stale "not defined in the preset" - // entry can never linger. - const sync = (selId, idxFn) => { - const sel = document.getElementById(selId); - if (!sel) return; - const idx = idxFn(); - const preset = idx >= 0 ? cards[idx] : null; - if (!preset) { - sel.disabled = true; - sel.innerHTML = ''; - return; - } - sel.disabled = false; - sel.innerHTML = spKeysetOptionsHtml(preset, kic, kid); - }; - sync('sp-keyset-sel', spPresetIdx); - sync('ram-keyset-sel', ramPresetIdx); - sync('tar-keyset-sel', tarPresetIdx); - // The start buttons follow the same state: they wait for a preset and a - // keyset defined in it. pysimApplyAvailability(); } @@ -13797,26 +13765,6 @@ function spKeysetApply(ks) { updateSp(); } -function spKeysetChanged() { - const sel = document.getElementById('sp-keyset-sel'); - if (!sel) return; - const kvn = parseInt(sel.value, 10); - const idx = spPresetIdx(); - const ks = (idx >= 0 && !isNaN(kvn)) ? spKeysetFor(cards[idx], kvn) : null; - if (ks) spKeysetApply(ks); - spKeysetSync(); -} - -function ramKeysetChanged() { - const sel = document.getElementById('ram-keyset-sel'); - if (!sel) return; - const kvn = parseInt(sel.value, 10); - const idx = ramPresetIdx(); - const ks = (idx >= 0 && !isNaN(kvn)) ? spKeysetFor(cards[idx], kvn) : null; - if (ks) spKeysetApply(ks); - spKeysetSync(); -} - // A warning for the SCP80/RAM views when the equipped card and the selected // preset disagree (pure). Autoselection keeps them in step, so this covers // what it cannot fix: a card with no preset at all, or a manually selected @@ -13869,30 +13817,64 @@ function ramPresetIdx() { // '' when the SCP80 form may build a packet, else the reason. With a preset // selected the keyset number must be one of its keysets; without one only the // byte rules apply (hand-entered keys stay possible, a workbench flow). +// The click-time guard (v3.26.0): the operation's TAR must have a usable +// default keyset in the preset - a TAR whose MSL requires security needs one +// (the counters are keyset-linked; MSL 00 may go keyless) - and the form's +// KIc/KID must stay consistent with it (the fields remain hand-editable). +function spTarKeysetGuard(preset, tar) { + if (!preset) return ''; + const entry = cardsTarEntry(preset, tar); + if (!entry) return ''; + if (entry.keyset) { + if (!spKeysetFor(preset, entry.keyset)) { + return t('Keyset') + ' ' + entry.keyset + ' ' + t('is not defined in the preset'); + } + return ''; + } + const msl = String(entry.msl || ''); + if (msl && msl !== '00') { + return t('TAR') + ' ' + entry.tar + ': ' + + t('needs a default keyset (its MSL requires security) - set it in the Cards tab'); + } + return ''; +} + function spKeysetGuard() { const idx = spPresetIdx(); + if (idx < 0) return ''; + const preset = cards[idx]; + const sel = document.getElementById('sp-tar'); + const tarErr = spTarKeysetGuard(preset, sel ? sel.value : ''); + if (tarErr) return tarErr; const kic = (document.getElementById('sp-kic-hex') || {}).value; const kid = (document.getElementById('sp-kid-hex') || {}).value; - return spKeysetCheck(idx >= 0 ? cards[idx] : null, kic, kid); + return spKeysetCheck(preset, kic, kid); } function ramKeysetGuard() { const idx = ramPresetIdx(); + if (idx < 0) return ''; + const preset = cards[idx]; + // The RAM operations are addressed to the ISD TAR (v3.26.0) + const tarErr = spTarKeysetGuard(preset, cardsRoleTar(preset, 'isd')); + if (tarErr) return tarErr; const kic = (document.getElementById('sp-kic-hex') || {}).value; const kid = (document.getElementById('sp-kid-hex') || {}).value; - return spKeysetCheck(idx >= 0 ? cards[idx] : null, kic, kid); + return spKeysetCheck(preset, kic, kid); } // Whether a view's start button may run: its preset must be selected and the -// form's keyset number one of that preset's keysets (the click-time guards keep -// the byte-level checks). '00'/'00' (no security) with a preset selected stays -// allowed - it is a deliberate form choice. +// operation TAR's keyset usable (the click-time guards keep the byte-level +// checks). '00'/'00' (no security) with a preset selected stays allowed - it +// is a deliberate form choice. function spKeysetStartReady(view) { - const idx = view === 'ram' ? ramPresetIdx() : (view === 'tar' ? tarPresetIdx() : spPresetIdx()); - if (idx < 0) return false; - const kic = (document.getElementById('sp-kic-hex') || {}).value; - const kid = (document.getElementById('sp-kid-hex') || {}).value; - return spKeysetCheck(cards[idx], kic, kid) === ''; + if (view === 'ram') { + if (ramPresetIdx() < 0) return false; + return ramKeysetGuard() === ''; + } + if (view === 'tar') return tarPresetIdx() >= 0; + if (spPresetIdx() < 0) return false; + return spKeysetGuard() === ''; } function cardsFormValues() { @@ -14257,40 +14239,22 @@ function cardsApplyFields(idx, keepTar, keepCntr) { const c = cards[parseInt(idx)]; if (!c) return false; // The TAR of the current operation (ISD for RAM/GP, the RFM TAR for the RFM - // chains) and its MSL: a preset apply seeds the SPI1 from the MSL - the - // card's Minimum SPI1 (TS 102 226 8.2.1.3.2.4). keepTar leaves the packet - // TAR alone (the plain send may target a manually entered TAR): its SPI1 is - // the operator's value too. + // chains) and its MSL + default keyset (v3.26.0): a preset apply selects + // the TAR in the selector and applies its keyset to the form, seeding the + // SPI1 from its MSL - the card's Minimum SPI1 (TS 102 226 8.2.1.3.2.4). + // keepTar leaves the packet TAR (and its keyset) alone: the plain send + // keeps the operator's selection. + spTarRender(c); if (!keepTar) { const roleTar = cardsRoleTar(c, _spTarKey); - document.getElementById('sp-tar').value = roleTar; - const msl = cardsTarMsl(c, roleTar) || CARDS_MSL_DEFAULT; - const spi1Sel = document.getElementById('sp-spi1'); - if (spi1Sel) { - if (spi1Sel.options && typeof Option === 'function' && - !Array.prototype.some.call(spi1Sel.options, o => o.value === msl)) { - spi1Sel.appendChild(new Option(msl + ' \u2014 MSL', msl)); - } - spi1Sel.value = msl; + const sel = document.getElementById('sp-tar'); + if (sel && Array.prototype.some.call(sel.options, o => o.value === roleTar)) { + sel.value = roleTar; } - } - // The keyset: keep the keyset number the form carries when this preset - // defines it, otherwise start from the preset's first keyset. - const kicNow = (document.getElementById('sp-kic-hex') || {}).value; - const kidNow = (document.getElementById('sp-kid-hex') || {}).value; - const kvnNow = spKeysetKvnOf(kicNow) || spKeysetKvnOf(kidNow) || 0; - const ks = (kvnNow && spKeysetFor(c, kvnNow)) || spKeysetList(c)[0] || null; - if (ks) { - const kicByte = parseInt(ks.kic, 16); - document.getElementById('sp-kic-idx').value = ((kicByte >> 4) & 0x0F).toString(16).toUpperCase(); - document.getElementById('sp-kic-alg').value = (kicByte & 0x0F).toString(16).padStart(2, '0'); - updateSpKic(); - const kidByte = parseInt(ks.kid, 16); - document.getElementById('sp-kid-idx').value = ((kidByte >> 4) & 0x0F).toString(16).toUpperCase(); - document.getElementById('sp-kid-alg').value = (kidByte & 0x0F).toString(16).padStart(2, '0'); - updateSpKid(); - document.getElementById('sp-kic-key').value = ks.kicKey || ''; - document.getElementById('sp-kid-key').value = ks.kidKey || ''; + const entry = cardsTarEntry(c, sel ? sel.value : roleTar); + spSpi1Seed((entry && entry.msl) || CARDS_MSL_DEFAULT); + const ks = (entry && entry.keyset) ? spKeysetFor(c, entry.keyset) : null; + if (ks) spKeysetApply(ks); } // The counter field is an override: applying a preset clears it (empty = // auto, the server seeds from the store); the pre-send refresh keeps a @@ -14301,8 +14265,6 @@ function cardsApplyFields(idx, keepTar, keepCntr) { if (cntrEl) cntrEl.value = ''; } spCntrPlaceholder(); - if (!keepTar) document.getElementById('sp-tar').value = cardsRoleTar(c, _spTarKey); - spTarListRender(c); spInvalidate(); updateSp(); spKeysetSync(); @@ -14317,7 +14279,7 @@ function cardsApply(idx, keepTar, keepCntr) { // warning must not outlive the selection it described. spKeysetSync(); spPresetWarningRender(); - spTarListRender(null); + spTarRender(null); return; } _genSpBuild(); @@ -23972,6 +23934,15 @@ const LANG_RU = { 'TARs (MSL)': 'TAR (MSL)', 'Each TAR carries its MSL (Minimum SPI1) — the card checks it before the security processing (TS 102 226 8.2.1.3.2.4); the packet SPI1 is derived from it.': 'У каждого TAR есть MSL (минимальный SPI1) — карта проверяет его до обработки защиты (TS 102 226 8.2.1.3.2.4); из него выводится SPI1 пакета.', 'More TARs — each with its MSL and an optional description': 'Дополнительные TAR — у каждого свой MSL и необязательное описание', + 'TAR (from the preset)': 'TAR (из пресета)', + 'TARs to probe (the preset\'s TAR list)': 'TAR для проверки (список TAR пресета)', + '— no TAR in the preset —': '— в пресете нет TAR —', + '— none —': '— нет —', + 'Select a card preset': 'Выберите пресет карты', + 'Select a card preset with TARs first': 'Сначала выберите пресет карты с TAR', + 'The keyset packets to this TAR use - a non-zero MSL requires one': 'Keyset, который используют пакеты к этому TAR — при MSL не 00 он обязателен', + 'This TAR needs a default keyset (its MSL requires security) - set it in the Cards tab': 'Этому TAR нужен keyset по умолчанию (его MSL требует защиты) — задайте его на вкладке «Карты»', + 'needs a default keyset (its MSL requires security) - set it in the Cards tab': 'нужен keyset по умолчанию (его MSL требует защиты) — задайте его на вкладке «Карты»', 'Add TAR': 'Добавить TAR', 'Remove TAR': 'Удалить TAR', 'Description (optional)': 'Описание (необязательно)', diff --git a/frontend/sw.js b/frontend/sw.js index 970b984..d6f48c1 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v433'; +const CACHE = 'simple-v434'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/cards_counter.test.js b/frontend/tests/cards_counter.test.js index ec9d7d1..4aff4ac 100644 --- a/frontend/tests/cards_counter.test.js +++ b/frontend/tests/cards_counter.test.js @@ -34,12 +34,12 @@ for (const fn of ['cardsTarList', 'cardsRoleDefault', 'cardsRoleTar', 'spPresetCounterValue', 'spCntrPlaceholder', 'spCntrEffective', 'spCntrAhead', 'spCntrAdjust', 'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck', - 'spKeysetOptionsHtml', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender', + 'spTarOptionsHtml', 'spTarRender', 'spSpi1Seed', 'spTarKeysetGuard', 'cardsTarKeysetCell', 'cardsTarKeysetFromForm', 'cardsTarKeysetRefresh', 'cardsTarRowsRender', 'cardsTarRowHtml', 'cardsTarsFromTable', 'cardsFreeTarsFromForm', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender', 'cardsKeysetAdd', 'cardsKeysetRemove', 'cardsKeysetKvnUpdate', 'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply', - 'spKeysetChanged', 'ramKeysetChanged', 'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard', - 'spTarListRender', + 'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard', 'ramKeysetGuard', + 'esc', ]) { code += extractFunc(html, fn) + '\n'; } @@ -61,9 +61,22 @@ globalThis.t = s => s; function fakeEnv(selValue, selId, presetCntr) { const els = {}; for (const id of ['sp-spi1', 'sp-spi2-hex', 'sp-kic-idx', 'sp-kic-alg', - 'sp-kid-idx', 'sp-kid-alg', 'sp-tar', 'sp-cntr', 'sp-kic-key', 'sp-kid-key']) { + 'sp-kid-idx', 'sp-kid-alg', 'sp-cntr', 'sp-kic-key', 'sp-kid-key']) { els[id] = { value: '' }; } + // the TAR is a select (v3.26.0): the stub models the surface the renderer + // touches - setting innerHTML parses the option values and selectedIndex + // moves the value, like a real |