release: v3.9.0 - the operation start buttons wait for a preset and a keyset

Gating: the SCP80 "Send to Card", the RAM Execute and the TAR-probe Execute
buttons carry `data-needs-keyset`; pysimApplyAvailability() disables them
(with the hint "Select a card preset and its keyset" when the card itself is
fine) unless the view's preset is selected and the form's keyset number is one
of its keysets - `spKeysetStartReady`, the same rule the click-time guards
enforce, which stay as the last line of defence.  spKeysetSync() refreshes the
gate on every preset/keyset change, and the KIc/KID index selects now call
spKeysetSync() so the keyset selector follows a manual index change too.

Version 3.9.0: server VERSION, pyproject.toml and the PWA header; sw cache
simple-v346; the workspace AGENTS gains its v3.9.0 history entry.

Tests: indicator.test.js drives the gate (no preset -> disabled with the hint,
an undefined keyset -> disabled, a matching keyset -> enabled) and guards the
three buttons' attribute; the cards/keysets/ram sandboxes stub
pysimApplyAvailability.  Help EN/RU notes the disabled start buttons.
This commit is contained in:
2026-10-01 22:03:36 +03:00
parent f261695b86
commit 0cccd62230
11 changed files with 91 additions and 15 deletions
+3 -3
View File
@@ -282,7 +282,7 @@
<p class="mb-3">Верхнеуровневая вкладка <strong>SCP80</strong> объединяет разделы, связанные с SCP80. Переключение — тремя переключателями: <strong>Secured Packet</strong>, <strong>RAM</strong> и <strong>TAR-сканер</strong>. Собирает защищённые пакеты SCP80 по ETSI TS 102 225. При подключении карты сервер читает её <strong>EF.ICCID</strong> (2FE2); если в пресете указан тот же номер (цифрами или сырым hex из EF), он автоматически выбирается во всех трёх представлениях SCP80. Вердикт <strong>низкого счётчика</strong> (<code class="font-mono text-sm">cntr_low</code> или предупреждающий ответ ENVELOPE, в котором всё ещё есть такой PoR) <em>не</em> исправляется повторной отправкой: любая операция SCP80/RAM немедленно останавливается, результат показывает счётчик карты и кнопку <strong>К пресету</strong>, открывающую соответствующий пресет на вкладке Cards, где счётчик нужно поднять выше &mdash; автоматически ничего не записывается.</p>
<h3 id="secured-packet" class="text-lg font-medium mb-2">3.1 Secured Packet</h3>
<p class="mb-2">Собирает защищённые пакеты SCP80 по ETSI TS 102 225. Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик; отправка отклоняется, если в пресете нет такого номера keyset или KIc/KID несут разные номера (TS 102 225 A.2). Селектор недоступен, пока не выбран пресет карты.</p>
<p class="mb-2">Собирает защищённые пакеты SCP80 по ETSI TS 102 225. Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик; отправка отклоняется, если в пресете нет такого номера keyset или KIc/KID несут разные номера (TS 102 225 A.2). Селектор недоступен, пока не выбран пресет карты. Кнопка запуска недоступна, пока не выбран пресет карты и один из его keysets.</p>
<h4 id="packet-structure" class="font-medium mb-1">Структура пакета</h4>
<table class="w-full text-sm mb-3 border-collapse">
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Поле</th><th class="text-left py-1 px-2">Размер</th><th class="text-left py-1 px-2">Описание</th></tr></thead>
@@ -314,7 +314,7 @@
<p class="text-sm mb-3">Кнопка <strong>Проверить в pySim</strong> сверяет собранный пакет с эталонной реализацией <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. Кнопка <strong>Отправить на карту</strong> доставляет пакет через ENVELOPE SMS-PP-DOWNLOAD (при подключении к серверу). 2-байтовый префикс CPL передаётся, когда пакет зашифрован или несёт RC/CC/DS (входит в их входные данные), а также при конкатенации SMS; одиночное незащищённое SMS сохраняет форму pySim, начинающуюся с CHL (TS 31.115 Table 1 NOTE). Под полем пакета показывается его размер и число SMS; пакет, не помещающийся в одно SMS, отправляется <strong>конкатенированной</strong> загрузкой (TS 31.115 &sect;4.3) &mdash; пакет делится на части SMS-пользовательских данных (первое SMS 132 октета, последующие по 134; первое дополнительно несёт информационные элементы конкатенации и CPI), и сегменты отправляются по порядку. Более 5 сегментов отклоняется: это практический предел буфера конкатенации карты. Полученный Proof of Receipt декодируется и показывается строкой статуса PoR (статус, TAR, счётчик, сырой PoR); статусное слово и данные ответа последней команды подставляются в подвкладку <strong>&laquo;Разбор R-APDU&raquo;</strong> (Remote APDU &rarr; &laquo;Разбор&raquo;), а успешный PoR увеличивает счётчик повторов (когда в SPI1 есть проверка счётчика &mdash; b5 b4 &ne; 00; без неё счётчик не увеличивается и не сохраняется) и очищает пакет.</p>
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Пресет карты со вкладки <strong>Карты</strong> обеспечивает SPI, ключи, TAR и счётчик (RAM использует <strong>ISD TAR</strong> пресета, по умолчанию <code class="font-mono text-sm">000000</code>). Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик операции. Селектор недоступен, пока не выбран пресет карты.</p>
<p class="mb-2">Выполняет операции удалённого управления приложениями (Remote Application Management) как защищённые пакеты SCP80 через SMS-PP-DOWNLOAD ENVELOPE. Карта должна поддерживать SCP03 (AES или 3DES). Пресет карты со вкладки <strong>Карты</strong> обеспечивает SPI, ключи, TAR и счётчик (RAM использует <strong>ISD TAR</strong> пресета, по умолчанию <code class="font-mono text-sm">000000</code>). Селектор <strong>Keyset</strong> рядом с выбором пресета указывает, из какого keyset берутся ключи и счётчик операции. Селектор недоступен, пока не выбран пресет карты. Кнопка запуска недоступна, пока не выбран пресет карты и один из его keysets.</p>
<h4 id="ram-operations" class="font-medium mb-1">Операции</h4>
<table class="w-full text-sm mb-3 border-collapse">
@@ -340,7 +340,7 @@
</section>
<section class="mb-10">
<h3 id="tar-probe" class="text-lg font-medium mb-2">3.3 TAR-сканер</h3>
<p class="mb-2">Третья пилюля вкладки SCP80 показывает, <strong>какие OTA-приложения зарегистрированы на карте</strong>: отправляется <strong>по одному защищённому пакету на каждый выбранный TAR</strong> &mdash; стандартные значения из TS 101 220 V18.3.0 Annex D, Table D.1, каждая строка несёт документ-источник &mdash; с безобидной C-APDU (по умолчанию <code class="font-mono text-sm">SELECT MF</code>), пресетом и keyset этого представления, SPI <code class="font-mono text-sm">16/01</code> (проверка счётчика + PoR).</p>
<p class="mb-2">Третья пилюля вкладки SCP80 показывает, <strong>какие OTA-приложения зарегистрированы на карте</strong>: отправляется <strong>по одному защищённому пакету на каждый выбранный TAR</strong> &mdash; стандартные значения из TS 101 220 V18.3.0 Annex D, Table D.1, каждая строка несёт документ-источник &mdash; с безобидной C-APDU (по умолчанию <code class="font-mono text-sm">SELECT MF</code>), пресетом и keyset этого представления, SPI <code class="font-mono text-sm">16/01</code> (проверка счётчика + PoR). Кнопка <strong>Execute</strong> недоступна, пока не выбран пресет карты и один из его keysets.</p>
<p class="mb-2">Список предзаполнен стандартными TAR (все отмечены; кнопки <strong>Выбрать все</strong> / <strong>Снять все</strong> переключают быстро), свои TAR добавляются кнопкой <strong>Добавить TAR</strong> (три hex-байта и необязательное описание). Пользовательские записи хранятся в <code class="font-mono text-sm">localStorage</code> по возможности &mdash; их потеря безвредна.</p>
<p class="mb-2">Каждый TAR классифицируется по ответу карты: <strong>зарегистрировано</strong> (ENVELOPE 9000 + PoR со SW и данными приложения), <strong>принято, нет ответа</strong> (PoR без R-APDU), <strong>принято, нет PoR</strong> (пакет молча отброшен) и <strong>отклонено</strong> (ENVELOPE не 9000, обычно <code class="font-mono text-sm">6200</code> для TAR без зарегистрированного приложения). Таблица результата появляется в окне операции; строка под кнопкой Execute показывает <code class="font-mono text-sm">зарегистрировано N / M</code>.</p>
<p class="mb-2">Каждый принятый картой пакет <strong>расходует счётчик</strong>, который сохраняется в keyset пресета &mdash; проба отклоняется, если в SPI1 нет проверки счётчика или в пресете нет такого номера keyset. На живой карте: <code class="font-mono text-sm">000000</code>, <code class="font-mono text-sm">B00000</code> и <code class="font-mono text-sm">B00001</code> зарегистрированы (приложения компактного формата), расширенный формат, USAT, мультиплексирование и CASD отклонены с <code class="font-mono text-sm">6200</code>.</p>
+3 -3
View File
@@ -281,7 +281,7 @@
<p class="mb-3">The <strong>SCP80</strong> top-level tab groups the SCP80-related views. It is switched by three pills: <strong>Secured Packet</strong>, <strong>RAM</strong> and <strong>TAR probe</strong>. Assembles SCP80 secured packets per ETSI TS 102 225. When a card is equipped the server reads its <strong>EF.ICCID</strong> (2FE2); if a card preset carries the same number (digits, or the raw EF hex), it is selected automatically in all three SCP80 views.</p>
<h3 id="secured-packet" class="text-lg font-medium mb-2">3.1 Secured Packet</h3>
<p class="mb-2">Builds SCP80 secured packets per ETSI TS 102 225. The <strong>Keyset</strong> selector next to the card preset selector picks which keyset of the preset supplies the keys and the counter; the send is refused when the preset does not define that keyset number or when its KIc/KID carry different numbers (TS 102 225 A.2). It is disabled until a card preset is selected.</p>
<p class="mb-2">Builds SCP80 secured packets per ETSI TS 102 225. The <strong>Keyset</strong> selector next to the card preset selector picks which keyset of the preset supplies the keys and the counter; the send is refused when the preset does not define that keyset number or when its KIc/KID carry different numbers (TS 102 225 A.2). It is disabled until a card preset is selected. The start button stays disabled until a card preset and one of its keysets are selected.</p>
<h4 id="packet-structure" class="font-medium mb-1">Packet structure</h4>
<table class="w-full text-sm mb-3 border-collapse">
<thead><tr class="border-b border-gray-300 dark:border-slate-700"><th class="text-left py-1 px-2">Field</th><th class="text-left py-1 px-2">Size</th><th class="text-left py-1 px-2">Description</th></tr></thead>
@@ -313,7 +313,7 @@
<p class="text-sm mb-3">A &ldquo;Verify vs pySim&rdquo; button cross-checks the assembled packet against pySim&rsquo;s reference <code class="font-mono text-sm">OtaDialectSms.encode_cmd</code>. A &ldquo;Send to Card&rdquo; button delivers it via SMS-PP-DOWNLOAD ENVELOPE (when connected to the server). The 2-byte CPL prefix is transmitted whenever the packet is ciphered or carries RC/CC/DS (it is part of their input) and whenever the packet is concatenated; a single unprotected SMS keeps pySim&rsquo;s CHL-first form (TS 31.115 Table 1 NOTE). Under the packet field the app reports its size and how many SMS it takes; a packet that does not fit one SMS is sent as a <strong>concatenated</strong> download (TS 31.115 &sect;4.3) &mdash; the packet is split into SMS user-data parts (first SM 132 octets, following ones 134; the first one additionally carries the concatenation and CPI information elements) and the segments are sent in order. More than 5 segments is refused, that being the practical limit of the card&rsquo;s concatenation buffer. The returned Proof of Receipt is decoded and shown as a PoR status line (status, TAR, counter, raw PoR); the last command&rsquo;s status word and response data are filled into the <strong>R-APDU parser</strong> panel, and a successful PoR advances the replay counter (when SPI1 carries a counter check &mdash; b5 b4 &ne; 00; without one no counter is advanced or saved) and clears the packet. A <strong>low-counter</strong> verdict (<code class="font-mono text-sm">cntr_low</code>, or a warning ENVELOPE answer that still carries such a PoR) is <em>not</em> recoverable by retrying: every SCP80/RAM operation stops immediately and the result states the card's counter with a <strong>Go to preset</strong> button that opens the matching preset in the Cards tab, where the counter must be raised above it &mdash; nothing is written automatically.</p>
<h3 id="ram" class="text-lg font-medium mb-2">3.2 RAM</h3>
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the SPI, keys, TAR, and counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default). The <strong>Keyset</strong> selector next to the preset selector picks the keyset whose keys and counter the operation uses. It is disabled until a card preset is selected.</p>
<p class="mb-2">Delivers Remote Application Management operations as SCP80 secured packets via SMS-PP-DOWNLOAD ENVELOPE. The card must support SCP03 (AES or 3DES). A saved card preset from the <strong>Cards</strong> tab provides the SPI, keys, TAR, and counter (RAM uses the preset's <strong>ISD TAR</strong>, <code class="font-mono text-sm">000000</code> by default). The <strong>Keyset</strong> selector next to the preset selector picks the keyset whose keys and counter the operation uses. It is disabled until a card preset is selected. The start button stays disabled until a card preset and one of its keysets are selected.</p>
<h4 id="ram-operations" class="font-medium mb-1">Operations</h4>
<table class="w-full text-sm mb-3 border-collapse">
@@ -339,7 +339,7 @@
</section>
<section class="mb-10">
<h3 id="tar-probe" class="text-lg font-medium mb-2">3.3 TAR probe</h3>
<p class="mb-2">The third SCP80 pill probes <strong>which OTA applications the card has registered</strong>: it sends <strong>one secured packet per selected TAR</strong> &mdash; the standard allocations of TS 101 220 V18.3.0 Annex D, Table D.1, each row carrying the document it comes from &mdash; with a harmless C-APDU (default <code class="font-mono text-sm">SELECT MF</code>) using the view's preset and keyset, SPI <code class="font-mono text-sm">16/01</code> (counter check + PoR).</p>
<p class="mb-2">The third SCP80 pill probes <strong>which OTA applications the card has registered</strong>: it sends <strong>one secured packet per selected TAR</strong> &mdash; the standard allocations of TS 101 220 V18.3.0 Annex D, Table D.1, each row carrying the document it comes from &mdash; with a harmless C-APDU (default <code class="font-mono text-sm">SELECT MF</code>) using the view's preset and keyset, SPI <code class="font-mono text-sm">16/01</code> (counter check + PoR). The <strong>Execute</strong> button stays disabled until a card preset and one of its keysets are selected.</p>
<p class="mb-2">The list is pre-filled with the standard TARs (all checked; <strong>Select all</strong> / <strong>Select none</strong> switch them quickly) and custom TARs can be added with <strong>Add TAR</strong> (three hex bytes plus an optional description). Custom entries are kept in <code class="font-mono text-sm">localStorage</code> on a best-effort basis &mdash; losing them is harmless.</p>
<p class="mb-2">Every TAR is classified from the card's answer: <strong>registered</strong> (ENVELOPE 9000 + a PoR carrying the application's own status word and data), <strong>accepted, no answer</strong> (a PoR without an R-APDU), <strong>accepted, no PoR</strong> (the packet was silently dropped) and <strong>refused</strong> (a non-9000 ENVELOPE, typically <code class="font-mono text-sm">6200</code> for a TAR without a registered application). The result table appears in the operation popup; the line under Execute summarises <code class="font-mono text-sm">registered N / M</code>.</p>
<p class="mb-2">Each packet the card accepts <strong>consumes a counter</strong>, which is saved into the preset's keyset &mdash; the probe is refused when SPI1 has no counter check or the named preset does not define the keyset number. Observed on a live card: <code class="font-mono text-sm">000000</code>, <code class="font-mono text-sm">B00000</code> and <code class="font-mono text-sm">B00001</code> registered (compact-format applications), the expanded-format TARs, USAT, multiplexing and CASD refused with <code class="font-mono text-sm">6200</code>.</p>
+28 -4
View File
@@ -676,7 +676,7 @@
<button onclick="genSp()" class="mb-3 px-5 py-2.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700" data-l10n="Generate Secure Packet">Generate Secure Packet</button>
<button data-needs="server" onclick="pysimVerifySp()" id="sp-verify-btn" class="hidden mb-3 px-5 py-2.5 bg-slate-600 text-white text-sm font-medium rounded hover:bg-slate-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Verify vs pySim">Verify vs pySim</button>
<button data-needs="card" onclick="pysimSendOta()" id="sp-send-btn" class="hidden mb-3 px-5 py-2.5 bg-emerald-600 text-white text-sm font-medium rounded hover:bg-emerald-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Send to Card">Send to Card</button>
<button data-needs="card" data-needs-keyset="sp" onclick="pysimSendOta()" id="sp-send-btn" class="hidden mb-3 px-5 py-2.5 bg-emerald-600 text-white text-sm font-medium rounded hover:bg-emerald-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Send to Card">Send to Card</button>
<div id="sp-por-status" class="mt-2 text-base font-semibold font-mono hidden"></div>
<textarea id="sp-result" rows="3" readonly class="w-full font-mono border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 bg-gray-100 dark:bg-slate-800"></textarea>
<div id="sp-size-info" class="text-xs mt-1 text-gray-500 dark:text-slate-400"></div>
@@ -708,7 +708,7 @@
<option value="make-selectable" data-l10n="INSTALL [for make selectable]">INSTALL [for make selectable]</option>
</select>
</div>
<button data-needs="card" data-cap-gate="ram" onclick="ramExecute()" class="px-5 py-1.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Execute">Execute</button>
<button data-needs="card" data-needs-keyset="ram" data-cap-gate="ram" onclick="ramExecute()" class="px-5 py-1.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Execute">Execute</button>
</div>
<div id="ram-preset-warning" class="hidden mb-3 px-3 py-2 rounded border border-amber-400 dark:border-amber-600 bg-amber-50 dark:bg-amber-900/20 text-sm text-amber-700 dark:text-amber-300"></div>
@@ -943,7 +943,7 @@
</div>
<label class="block mb-1 text-sm font-medium text-gray-700 dark:text-slate-300" data-l10n="Probe APDU (hex)">Probe APDU (hex)</label>
<input id="tar-apdu" class="w-full border border-gray-300 dark:border-slate-600 text-sm rounded px-3 py-1.5 dark:bg-slate-800 font-mono mb-3" value="00A40000023F00">
<button data-needs="card" onclick="tarExecute()" class="px-5 py-1.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Execute">Execute</button>
<button data-needs="card" data-needs-keyset="tar" onclick="tarExecute()" class="px-5 py-1.5 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 disabled:opacity-40 disabled:cursor-not-allowed" data-l10n="Execute">Execute</button>
<div id="tar-result" class="mt-2 text-base font-semibold font-mono hidden"></div>
</div>
</div>
@@ -1791,7 +1791,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.8.0';
const SIMPLE_VERSION = '3.9.0';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching =====
@@ -4745,6 +4745,7 @@ function updateSpKic() {
document.getElementById('sp-kic-hex').value = byte.toString(16).padStart(2, '0').toUpperCase();
updateKeyPlaceholder('kic', alg & 0x0F);
spInvalidate();
spKeysetSync();
}
function updateSpKid() {
const idx = parseInt(document.getElementById('sp-kid-idx').value, 16);
@@ -4753,6 +4754,7 @@ function updateSpKid() {
document.getElementById('sp-kid-hex').value = byte.toString(16).padStart(2, '0').toUpperCase();
updateKeyPlaceholder('kid', alg & 0x0F);
spInvalidate();
spKeysetSync();
}
function updateKeyPlaceholder(prefix, algNib) {
const keyLen = {1: 8, 2: 16, 5: 16, 9: 24, 0xD: 8}[algNib] || 8;
@@ -7715,7 +7717,13 @@ function pysimApplyAvailability() {
// From-card buttons need a readable ICCID, not just a session.
disabled = pysimControlDisabled('card', state) || !_pysimCardIccid;
}
const ksView = el.getAttribute('data-needs-keyset');
const ksBlocked = ksView && !spKeysetStartReady(ksView);
if (ksBlocked) disabled = true;
let hint = disabled ? pysimNeedsHint(needs, state) : null;
// The generic hint is "Insert and equip a card": with the card fine,
// the missing preset/keyset is the real blocker.
if (ksBlocked && state === 'card') hint = t('Select a card preset and its keyset');
if (el.hasAttribute('data-needs-decoder') && !pysimFsHasDecoder()) {
// Client-side decoder required (Read decoded) — the card hint wins
// when the card itself is the blocker.
@@ -11485,6 +11493,9 @@ function spKeysetSync() {
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();
}
function spKeysetApply(ks) {
@@ -11593,6 +11604,18 @@ function ramKeysetGuard() {
return spKeysetCheck(idx >= 0 ? cards[idx] : null, 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.
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) === '';
}
function cardsFormValues() {
const val = id => {
const el = document.getElementById('cards-' + id);
@@ -19856,6 +19879,7 @@ const LANG_RU = {
'not defined in the preset': 'не определён в пресете',
'— no keyset —': '— нет keyset —',
'— select a card preset —': '— выберите пресет карты —',
'Select a card preset and its keyset': 'Выберите пресет карты и его keyset',
'counter': 'счётчик',
'Define at least one keyset (KIc/KID and their keys)': 'Определите хотя бы один keyset (KIc/KID и его ключи)',
'Each keyset needs KIc, KID and both keys': 'Каждому набору нужны KIc, KID и оба ключа',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simple-v345';
const CACHE = 'simple-v346';
const URLS = [
'index.html',
'help.html',
+2 -1
View File
@@ -33,7 +33,8 @@ for (const fn of ['cardsTarValue', 'cardsApplyFields', 'cardsApply',
]) {
code += extractFunc(html, fn) + '\n';
}
code = 'var _cardsKeysetCount = 0;\nglobalThis.spPresetWarningRender = function() {};\n' + code;
code = 'var _cardsKeysetCount = 0;\nglobalThis.spPresetWarningRender = function() {};\n'
+ 'globalThis.pysimApplyAvailability = function() {};\n' + code;
code = 'var _genSpBuildStub = function() {};\n' + code;
eval(code);
globalThis._genSpBuild = () => {};
+2 -1
View File
@@ -37,7 +37,8 @@ for (const fn of ['cardsTarValue', 'cardsFormValues', 'cardsClearForm', 'cardsEd
'ramKeysetChanged', 'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard', 'ramKeysetGuard']) {
code += extractFunc(html, fn, ASYNC_FNS.indexOf(fn) >= 0) + '\n';
}
code = 'var _cardsKeysetCount = 0;\nglobalThis.spPresetWarningRender = function() {};\n' + code;
code = 'var _cardsKeysetCount = 0;\nglobalThis.spPresetWarningRender = function() {};\n'
+ 'globalThis.pysimApplyAvailability = function() {};\n' + code;
eval(code);
const CARD_IDS = ['cards-name','cards-iccid','cards-adm',
+48
View File
@@ -34,6 +34,14 @@ code += extractFunc(html, 'pysimUpdatePresetIndicators') + '\n';
code += extractFunc(html, 'cardsScp80Complete') + '\n';
code += extractFunc(html, 'cardsScp81Complete') + '\n';
code += extractFunc(html, 'cardsMatchedPreset') + '\n';
code += extractFunc(html, 'spKeysetStartReady') + '\n';
code += extractFunc(html, 'spPresetIdx') + '\n';
code += extractFunc(html, 'ramPresetIdx') + '\n';
code += extractFunc(html, 'tarPresetIdx') + '\n';
code += extractFunc(html, 'spKeysetCheck') + '\n';
code += extractFunc(html, 'spKeysetFor') + '\n';
code += extractFunc(html, 'spKeysetList') + '\n';
code += extractFunc(html, 'spKeysetKvnOf') + '\n';
code += 'globalThis.t = s => s;\n';
code += 'globalThis.testRunActive = () => false;\n';
eval(code);
@@ -287,3 +295,43 @@ test('card action labels carry the equipped ICCID', () => {
assert.strictEqual(plain.textContent, '');
assert.strictEqual(colon.textContent, '');
});
test('the start buttons wait for a preset and a keyset defined in it', () => {
const run = (presetIdx, kic, kid) => {
const el = fakeEl();
el.setAttribute('data-needs', 'card');
el.setAttribute('data-needs-keyset', 'sp');
const els = {
'sp-card-sel': { value: presetIdx },
'sp-kic-hex': { value: kic },
'sp-kid-hex': { value: kid },
};
globalThis.document = { querySelectorAll: () => [el], getElementById: id => els[id] || null };
globalThis.cards = [{ name: 'EP', keysets: [
{ kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' }] }];
_pysimServerAvailable = true;
_pysimCardEquipped = true;
pysimApplyAvailability();
return el;
};
// no preset selected -> disabled with the hint
let el = run('', '15', '15');
assert.strictEqual(el.disabled, true);
assert.strictEqual(el.attrs.title, 'Select a card preset and its keyset');
// a keyset number the preset does not define -> disabled
el = run('0', '35', '35');
assert.strictEqual(el.disabled, true);
// a keyset of the preset -> enabled, no tooltip
el = run('0', '15', '15');
assert.strictEqual(el.disabled, false);
assert.strictEqual(el.attrs.title, undefined);
});
test('the SCP80/RAM/TAR start buttons carry the keyset gate', () => {
const sp = /<button[^>]*id="sp-send-btn"[^>]*>/.exec(html)[0];
assert.match(sp, /data-needs-keyset="sp"/);
const ram = /<button[^>]*onclick="ramExecute\(\)"[^>]*>/.exec(html)[0];
assert.match(ram, /data-needs-keyset="ram"/);
const tar = /<button[^>]*onclick="tarExecute\(\)"[^>]*>/.exec(html)[0];
assert.match(tar, /data-needs-keyset="tar"/);
});
+1
View File
@@ -28,6 +28,7 @@ for (const fn of ['spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck
code += extractFunc(html, fn) + '\n';
}
code += 'function t(s){return s;}\n';
code += 'globalThis.pysimApplyAvailability = () => {};\n';
eval(code);
const PRESET = {
+1
View File
@@ -50,6 +50,7 @@ if (an) code += an[0].replace(/^const /, 'var ') + '\n';
const ar = html.match(/const JC_AID_RIDS = \{[\s\S]*?\n\};/);
if (ar) code += ar[0].replace(/^const /, 'var ') + '\n';
globalThis.spPresetWarningRender = () => {};
globalThis.pysimApplyAvailability = () => {};
eval(code);
code += 'var _ramCardIdx = null;\nvar _ramOpLast = null;\nvar _ramExplorerData = null;\n';
eval(code);