diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 522c65d..4b2823a 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -346,7 +346,7 @@

Каждый принятый картой пакет расходует счётчик, который сохраняется в keyset пресета — проба отклоняется, если в SPI1 нет проверки счётчика или в пресете нет такого номера keyset. На живой карте: 000000, B00000 и B00001 зарегистрированы (приложения компактного формата), расширенный формат, USAT, мультиплексирование и CASD отклонены с 6200.

4. Вкладка «Карты»

-

Хранит пресеты карт на сервере (~/.pysim-simple-server/card_presets.json; переопределяется --card-presets), чтобы представление Secured Packet могло автоматически подставлять ключи и параметры, слушатель SCP81 HTTP OTA — находить PSK-ключи, а счётчики SCP80, израсходованные операцией, сохранялись самой операцией — закрытая вкладка или второе окно браузера больше не могут потерять инкремент. Пресет содержит несколько keysets — номер keyset задаётся нибблом b8..b5 его KIc/KID (TS 102 225 §5.1.2/A.2): KIc и KID одного keyset должны нести один и тот же номер (01–0F; A.2 отвергает несовпадение), и у каждого keyset на карте свой счётчик (Annex A.1), поэтому счётчики ведутся по keysets. В форме keysets редактируются строками — по одной на номер, с кнопками Добавить keyset / Удалить keyset; номер в строке вычисляется из байта KIc/KID, а 00 («без защиты») не принимается как keyset. Список загружается при подключении PWA и экспортируется/импортируется как JSON (импорт принимает и пресеты старых сборок — плоские и с keysets). «Карты» — верхнеуровневая вкладка. При подключении карты читается её EF.ICCID, и пресет с тем же ICCID автоматически выбирается во всех трёх представлениях SCP80; если подключённая карта не совпадает ни с одним пресетом — или выбранный пресет принадлежит другой карте — все три представления SCP80 показывают жёлтое предупреждение над выбором пресета. Поля формы сгруппированы в два блока с рамкой: SCP80 (GSM 03.48, ETSI TS 102 225) (KIc/KID, SPI1/SPI2, TAR-ы ISD/UICC RFM/ADF RFM, счётчик и ключи) и SCP81 (HTTP OTA) (пара PSK identity/ключ с пояснением о выборе ключа). Необязательное поле ADM находится в верхней строке и сохраняется для последующего использования файловым менеджером. В шапке отображаются серые маркеры SCP80/SCP81 и значок ключа на бейдже ADM, когда в пресете с ICCID подключённой карты эти поля заполнены.

+

Хранит пресеты карт на сервере (~/.pysim-simple-server/card_presets.json; переопределяется --card-presets), чтобы представление Secured Packet могло автоматически подставлять ключи и параметры, слушатель SCP81 HTTP OTA — находить PSK-ключи, а счётчики SCP80, израсходованные операцией, сохранялись самой операцией — закрытая вкладка или второе окно браузера больше не могут потерять инкремент. Пресет содержит несколько keysets — номер keyset задаётся нибблом b8..b5 его KIc/KID (TS 102 225 §5.1.2/A.2): KIc и KID одного keyset должны нести один и тот же номер (01–0F; A.2 отвергает несовпадение), и у каждого keyset на карте свой счётчик (Annex A.1), поэтому счётчики ведутся по keysets. В форме keysets редактируются строками — по одной на номер, с кнопками Добавить keyset / Удалить keyset; номер в строке вычисляется из байта KIc/KID, а 00 («без защиты») не принимается как keyset. Список загружается при подключении PWA и экспортируется/импортируется как JSON (импорт принимает и пресеты старых сборок — плоские и с keysets). «Карты» — верхнеуровневая вкладка. При подключении карты читается её EF.ICCID, и пресет с тем же ICCID автоматически выбирается во всех трёх представлениях SCP80; если подключённая карта не совпадает ни с одним пресетом — или выбранный пресет принадлежит другой карте — все три представления SCP80 показывают жёлтое предупреждение над выбором пресета. В списке подсвечивается пресет подключённой карты. Поля формы сгруппированы в два блока с рамкой: SCP80 (GSM 03.48, ETSI TS 102 225) (KIc/KID, SPI1/SPI2, TAR-ы ISD/UICC RFM/ADF RFM, счётчик и ключи) и SCP81 (HTTP OTA) (пара PSK identity/ключ с пояснением о выборе ключа). Необязательное поле ADM находится в верхней строке и сохраняется для последующего использования файловым менеджером. В шапке отображаются серые маркеры SCP80/SCP81 и значок ключа на бейдже ADM, когда в пресете с ICCID подключённой карты эти поля заполнены.

diff --git a/frontend/help.html b/frontend/help.html index a3fb412..5247678 100644 --- a/frontend/help.html +++ b/frontend/help.html @@ -345,7 +345,7 @@

Each packet the card accepts consumes a counter, which is saved into the preset's keyset — 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: 000000, B00000 and B00001 registered (compact-format applications), the expanded-format TARs, USAT, multiplexing and CASD refused with 6200.

4. Cards tab

-

Stores card presets on the server (~/.pysim-simple-server/card_presets.json; --card-presets overrides it) so the Secured Packet view can auto-fill keys and parameters, the SCP81 HTTP OTA listener can look up PSK keys, and the SCP80 counters an operation consumed are persisted with the operation itself — a closed tab or a second browser window can no longer lose an increment. A preset carries several keysets — a keyset's number is the b8..b5 nibble of its KIc/KID (TS 102 225 §5.1.2/A.2): KIc and KID of a keyset must carry the same number (01–0F; A.2 rejects a mismatch), and each keyset has its own counter on the card (Annex A.1), so counters are tracked per keyset. In the form the keysets are edited as rows — one per number, with Add keyset / Remove keyset; each row shows the number derived from its KIc/KID byte, and 00 (no security) is not accepted as a keyset. The list is loaded when the PWA connects and can be exported/imported as JSON (the import also takes presets exported by older builds, flat or keyset-shaped). 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 all three SCP80 views; when the equipped card matches no preset — or the selected preset belongs to another card — all three SCP80 views show an amber warning above the preset selector. The form groups the fields into two bordered blocks: SCP80 (GSM 03.48, ETSI TS 102 225) (KIc/KID, SPI1/SPI2, the ISD/UICC RFM/ADF RFM TARs, counter and keys) and SCP81 (HTTP OTA) (the PSK identity/key pair with the key-selection note). The optional ADM field sits in the top row and is stored for later use by the file manager. The header shows gray SCP80/SCP81 markers and a key glyph on the ADM badge when the preset matching the equipped card's ICCID has those settings filled in.

+

Stores card presets on the server (~/.pysim-simple-server/card_presets.json; --card-presets overrides it) so the Secured Packet view can auto-fill keys and parameters, the SCP81 HTTP OTA listener can look up PSK keys, and the SCP80 counters an operation consumed are persisted with the operation itself — a closed tab or a second browser window can no longer lose an increment. A preset carries several keysets — a keyset's number is the b8..b5 nibble of its KIc/KID (TS 102 225 §5.1.2/A.2): KIc and KID of a keyset must carry the same number (01–0F; A.2 rejects a mismatch), and each keyset has its own counter on the card (Annex A.1), so counters are tracked per keyset. In the form the keysets are edited as rows — one per number, with Add keyset / Remove keyset; each row shows the number derived from its KIc/KID byte, and 00 (no security) is not accepted as a keyset. The list is loaded when the PWA connects and can be exported/imported as JSON (the import also takes presets exported by older builds, flat or keyset-shaped). 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 all three SCP80 views; when the equipped card matches no preset — or the selected preset belongs to another card — all three SCP80 views show an amber warning above the preset selector. The list highlights the equipped card's preset. The form groups the fields into two bordered blocks: SCP80 (GSM 03.48, ETSI TS 102 225) (KIc/KID, SPI1/SPI2, the ISD/UICC RFM/ADF RFM TARs, counter and keys) and SCP81 (HTTP OTA) (the PSK identity/key pair with the key-selection note). The optional ADM field sits in the top row and is stored for later use by the file manager. The header shows gray SCP80/SCP81 markers and a key glyph on the ADM badge when the preset matching the equipped card's ICCID has those settings filled in.

ПолеОписание
diff --git a/frontend/index.html b/frontend/index.html index 031058c..77fdefd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -11741,17 +11741,26 @@ function cardsRender() { tbody.innerHTML = ''; return; } + // The preset of the equipped card (normalised ICCID match): its row is + // highlighted so the list shows which preset belongs to the card in the + // reader (no highlight while the ICCID is unknown). + const equippedIdx = cardsFindByIccid(_pysimCardIccid); let html = ''; for (let i = 0; i < cards.length; i++) { const c = cards[i]; + const equipped = i === equippedIdx; let ota = '—'; if (cardsScp81Complete(c)) { ota = '✓'; } else if (c.pskKey || c.pskIdentity) { ota = '⚠'; } - html += ''; - html += ''; + html += ''; + html += ''; html += ''; const ksText = spKeysetList(c).map(ks => { const kvn = spKeysetKvnOf(ks.kic); @@ -13794,7 +13803,9 @@ function pysimCardStateUpdate(status) { _pysimLastStatus = status; _pysimCardEquipped = !!status.connected; _pysimEquipping = !!status.equipping; + const prevIccid = _pysimCardIccid; _pysimCardIccid = status.connected ? (status.iccid || null) : null; + if (prevIccid !== _pysimCardIccid) cardsRender(); // equipped-preset highlight pysimUpdateAdmIndicator(status); pysimUpdateIccidIndicator(_pysimCardIccid); pysimUpdatePresetIndicators(_pysimCardIccid); @@ -19197,6 +19208,7 @@ const LANG_RU = { 'Snapshot comparison results:': 'Результаты сравнения снимков:', 'RAM operations perform atomic GlobalPlatform commands over SCP80. Card keys are taken from the saved preset.': 'RAM-операции выполняют атомарные команды GlobalPlatform через SCP80. Ключи карты берутся из сохранённого пресета.', 'No cards defined.': 'Карты не заданы.', + "The equipped card's preset": 'Пресет подключённой карты', 'Card presets': 'Выбор карты', 'Card preset': 'Пресет карты', '— Select card —': '— Выберите карту —', diff --git a/frontend/sw.js b/frontend/sw.js index 1f75208..6930229 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v343'; +const CACHE = 'simple-v344'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/card_state.test.js b/frontend/tests/card_state.test.js index da60e64..5e31929 100644 --- a/frontend/tests/card_state.test.js +++ b/frontend/tests/card_state.test.js @@ -51,6 +51,7 @@ code += extractFunc(html, 'cardsScp81Complete') + '\n'; code += extractFunc(html, 'pysimSetServerAvailable') + '\n'; code += '\nglobalThis.esc = s => s;\n'; code += 'globalThis.t = s => s;\n'; +code += 'globalThis.cardsRender = () => {};\n'; // equipped-preset highlight eval(code); function fakeIndicator() { diff --git a/frontend/tests/cards_iccid.test.js b/frontend/tests/cards_iccid.test.js index 3afaf03..82428eb 100644 --- a/frontend/tests/cards_iccid.test.js +++ b/frontend/tests/cards_iccid.test.js @@ -24,7 +24,8 @@ function extractFunc(src, name) { let code = ''; for (const fn of ['swapNibbles', 'encIccid', 'decIccid', 'cardsNormIccid', 'cardsFindByIccid', 'cardsFindDuplicateIccid', - 'cardsIccidFromCard', 'cardsAutoSelectByIccid']) { + 'cardsIccidFromCard', 'cardsAutoSelectByIccid', 'cardsRender', + 'esc', 'spKeysetList', 'spKeysetKvnOf', 'cardsTarValue', 'cardsScp81Complete']) { code += extractFunc(html, fn) + '\n'; } eval(code); @@ -169,3 +170,28 @@ test('the card-state update wires the ICCID into the preset selection', () => { 'the auto-selection must cover the TAR probe view'); assert.ok(html.includes("(data.iccid ? ' | ICCID: ' + esc(data.iccid) + '' : '')")); }); + +test('cardsRender highlights the equipped card preset', () => { + globalThis.t = s => s; + const els = { 'cards-tbody': { innerHTML: '' } }; + globalThis.document = { getElementById: id => els[id] || null }; + globalThis.cards = [ + { name: 'other', iccid: '1111111111111111111', spi1: '16', spi2: '01', keysets: [] }, + { name: 'live', iccid: DIGITS, spi1: '16', spi2: '01', keysets: [] }, + ]; + globalThis._pysimCardIccid = DIGITS; + cardsRender(); + let rows = els['cards-tbody'].innerHTML.split(''); + assert.ok(!/bg-emerald-50/.test(rows[0]), 'the other preset must stay plain'); + assert.match(rows[1], /bg-emerald-50/); + assert.match(rows[1], /The equipped card's preset/); + // the same card stored as raw EF hex matches too + globalThis.cards[1].iccid = RAW_HEX; + cardsRender(); + rows = els['cards-tbody'].innerHTML.split(''); + assert.match(rows[1], /bg-emerald-50/); + // no readable ICCID -> no highlight + globalThis._pysimCardIccid = null; + cardsRender(); + assert.ok(!/bg-emerald-50/.test(els['cards-tbody'].innerHTML)); +});
FieldDescription
' + t('No cards defined.') + '
' + esc(c.name) + '
' + + (equipped ? '●' : '') + + esc(c.name) + '' + esc(c.iccid) + '