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.
Хранит пресеты карт на сервере (~/.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 подключённой карты эти поля заполнены.
| Поле | Описание | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| Field | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| ' + t('No cards defined.') + ' | |||||||||
| ' + esc(c.name) + ' | '; + html += '|||||||||
| ' + + (equipped ? '●' : '') + + esc(c.name) + ' | '; html += '' + esc(c.iccid) + ' | '; 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('||||||||