Хранит предустановки карт локально в браузере (localStorage), чтобы представление Secured Packet могло автоматически подставлять ключи и параметры, а слушатель SCP81 HTTP OTA — находить PSK-ключи. «Карты» — верхнеуровневая вкладка. При подключении карты читается её EF.ICCID, и предустановка с тем же ICCID автоматически выбирается в обоих представлениях SCP80.
+
Хранит предустановки карт локально в браузере (localStorage), чтобы представление Secured Packet могло автоматически подставлять ключи и параметры, а слушатель SCP81 HTTP OTA — находить PSK-ключи. «Карты» — верхнеуровневая вкладка. При подключении карты читается её EF.ICCID, и предустановка с тем же ICCID автоматически выбирается в обоих представлениях SCP80. Поля формы сгруппированы в два блока с рамкой: SCP80 (GSM 03.48, ETSI TS 102 225) (KIc/KID, SPI1/SPI2, TAR, счётчик и ключи) и SCP81 (HTTP OTA) (пара PSK identity/ключ с пояснением о выборе ключа).
Stores card presets locally in the browser (localStorage) so the Secured Packet view can auto-fill keys and parameters, and so the SCP81 HTTP OTA listener can look up PSK keys. Cards is a top-level tab. When the card is equipped its EF.ICCID is read and the preset with the same ICCID is selected automatically in both SCP80 views.
+
Stores card presets locally in the browser (localStorage) so the Secured Packet view can auto-fill keys and parameters, and so the SCP81 HTTP OTA listener can look up PSK keys. Cards is a top-level tab. When the card is equipped its EF.ICCID is read and the preset with the same ICCID is selected automatically in both SCP80 views. The form groups the fields into two bordered blocks: SCP80 (GSM 03.48, ETSI TS 102 225) (KIc/KID, SPI1/SPI2, TAR, counter and keys) and SCP81 (HTTP OTA) (the PSK identity/key pair with the key-selection note).
SCP81 HTTP OTA: the listener picks the key by the PSK identity the card sends in the TLS handshake. Cards without a PSK are ignored by the listener.
+
+
@@ -11666,6 +11672,27 @@ const LANG_RU = {
'pending': 'ожидает',
'Name is required': 'Укажите имя',
'From card': 'С карты',
+ 'SCP80 (GSM 03.48, ETSI TS 102 225)': 'SCP80 (GSM 03.48, ETSI TS 102 225)',
+ 'SCP81 (HTTP OTA)': 'SCP81 (HTTP OTA)',
+ 'Start': 'Запустить',
+ 'Clear': 'Очистить',
+ 'Loading...': 'Загрузка...',
+ 'Working...': 'Выполняется...',
+ 'APDU hex': 'APDU (hex)',
+ 'CAP file (max 48 kB)': 'CAP-файл (не более 48 кБ)',
+ 'Install from .cap': 'Установить из .cap',
+ 'RAM': 'RAM',
+ 'SCP81': 'SCP81',
+ 'Secured packet': 'Secured packet',
+ 'Card reader server is not connected': 'Сервер картридера не подключён',
+ 'KIc and KID are required': 'Укажите KIc и KID',
+ 'KIc key and KID key are required': 'Укажите ключ KIc и ключ KID',
+ 'Memory (GET DATA FF21)': 'Память (GET DATA FF21)',
+ 'No secured packet to send. Generate a secure packet first.': 'Нет защищённого пакета для отправки. Сначала соберите пакет.',
+ 'No secured packet to verify. Generate a secure packet first.': 'Нет защищённого пакета для проверки. Сначала соберите пакет.',
+ 'Select': 'Выборка',
+ 'event notification to the card?': 'уведомление о событии на карту?',
+ 'SCP81 HTTP OTA: the listener picks the key by the PSK identity the card sends in the TLS handshake. Cards without a PSK are ignored by the listener.': 'SCP81 HTTP OTA: слушатель выбирает ключ по PSK identity, которую карта присылает в TLS-рукопожатии. Карты без PSK слушателем игнорируются.',
'Card with this ICCID already exists': 'Карта с таким ICCID уже есть в списке',
'Card equipped but its ICCID is not readable': 'Карта подключена, но её ICCID не читается',
'Saved.': 'Сохранено.',
diff --git a/frontend/sw.js b/frontend/sw.js
index 5b4f48a..102d73b 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'otaman-v185';
+const CACHE = 'otaman-v186';
const URLS = [
'index.html',
'help.html',
diff --git a/frontend/tests/html.test.js b/frontend/tests/html.test.js
index 62ba639..09ce94f 100644
--- a/frontend/tests/html.test.js
+++ b/frontend/tests/html.test.js
@@ -25,6 +25,21 @@ test('cards list shows the SCP81 PSK column with blue/red row buttons', () => {
assert.match(fn[0], /cardsRemove\(' \+ i \+ '\)" class="[^"]*bg-red-600 text-white/);
});
+test('the card form groups the SCP80 and SCP81 fields into labelled fieldsets', () => {
+ const fieldsets = [...html.matchAll(/