esim: drive the profile switch ourselves; guard the FCP metadata

Disabling a profile failed with 6985 and left the card stuck until an
equip.  pySim's send_apdu_checksw auto-handler keeps flushing proactive
commands after the REFRESH TERMINAL RESPONSE; the card is then mid-switch
and answers 6985 to the next FETCH, which propagated as a 500 and skipped
the re-initialization.  Per SGP.22 v2.6 5.7.16/5.7.17 a 91XX answer is
the ISD-R's 'result OK before REFRESH' (step 6) and the switch completes
on the TERMINAL RESPONSE or the following RESET (step 8) - lpac treats
91XX the same way and never retries.

- esim.py: build_switch_apdu/parse_switch_response/switch_profile split
  out of set_profile_state; the switch is one raw STORE DATA via
  scc._tp.send_apdu, a 91XX runs our own FETCH/TR chain (status_poll=False)
  and is reported as ok, the STORE DATA is never retried and a chain
  failure still counts the accepted switch.
- server.py: /api/esim/profile answers the REFRESH with our chain, then
  re-initializes the card and re-reads the profile list, returning
  verified/state_after; _handle_proactive_chain grew status_poll.
- /api/status and /api/select: FCP metadata via _fcp_value - an ADF or a
  failed select (card with the active profile disabled) has no
  file_descriptor and used to crash the request handler; _get_file_type
  no longer raises either.
- esim._restore logs a failed selection restore instead of swallowing it.
- PWA: esimSwitchStatus shows the verified state / not-confirmed warning.
- tests: the switch flow (9000 / 91XX / error SW / chain failure), the
  FCP guards and the status helper; docs and sw simple-v230.
This commit is contained in:
2026-09-22 00:14:02 +03:00
parent 91c642a646
commit 86808ce55d
11 changed files with 338 additions and 87 deletions
+1 -1
View File
@@ -569,7 +569,7 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
**Монитор сетевого состояния** — компактная панель **«Сетевое состояние»** рядом с кнопками симуляции показывает, что сейчас хранит карта и что было сэмулировано последним. В заголовке — **сэмулированное состояние сервиса** (*Не определено*, пока его не задаст сценарий или событие Location status, затем *Обычный сервис* / *Ограниченный сервис* / *Нет сервиса*) с пометкой *PLMN не разрешён*, если location-файлы или EF.FPLMN указывают на отказ, плюс текущее местоположение: PLMN, страна и оператор, LAI/RAI/TAI и **класс роуминга** (*Домашняя сеть*, если PLMN совпадает с HPLMN; *Эквивалентная домашней*, если он есть в EF.EHPLMN; иначе *Гостевая (роуминг)*). Ниже — по одной компактной строке на контролируемый файл (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) с декодированной сводкой и признаком последнего обновления (`init`, `write`, `read`, `refresh`); при наведении — все декодированные поля; длинные списки PLMN сокращаются (EF.HPLMNwAcT показывает только первую сеть и пометку `… +N`). Панель читает файлы один раз при подключении карты (только если ICCID читается), обновляет их на месте по записанным симулятором байтам, перечитывает EF.IMSI после каждого сценария и события Location status и никогда не опрашивает карту — кнопка **«Обновить»** перечитывает все файлы по требованию.
**eSIM** — для eUICC (SGP.22/SGP.32) подвкладка **«eSIM»** читает чип и управляет установленными профилями через локальный интерфейс ES10 (через pySim, без обращения к SM-DP+): **Chip** (EID, EUICCInfo1/2 с декодированными битовыми списками возможностей, ресурсами карты, идентификаторами CI PKI, категорией, запрещёнными PPR, данными сертификации; настроенные адреса SM-DP+ по умолчанию / корневого DS; таблица авторизации правил из ES10b GetRat), **Profiles** (состояние, nickname, провайдер, ICCID, AID ISD-P, класс, владелец; **Enable**/**Disable** переключает профиль — карта обычно сначала присылает REFRESH, после чего сессия карты переинициализируется как при equip, и ICCID, сетевое состояние и все кэшированные представления перечитываются) и **Notifications** (список ожидающих уведомлений, только чтение). Никаких загрузок профилей, обработки уведомлений и взаимодействия с SM-DP+ — используются только локальные функции ES10a/b/c; для карты не-eUICC подвкладка сообщает об этом.
**eSIM** — для eUICC (SGP.22/SGP.32) подвкладка **«eSIM»** читает чип и управляет установленными профилями через локальный интерфейс ES10 (через pySim, без обращения к SM-DP+): **Chip** (EID, EUICCInfo1/2 с декодированными битовыми списками возможностей, ресурсами карты, идентификаторами CI PKI, категорией, запрещёнными PPR, данными сертификации; настроенные адреса SM-DP+ по умолчанию / корневого DS; таблица авторизации правил из ES10b GetRat), **Profiles** (состояние, nickname, провайдер, ICCID, AID ISD-P, класс, владелец; **Enable**/**Disable** переключает профиль — карта обычно сначала присылает REFRESH, после чего сессия карты переинициализируется как при equip, и ICCID, сетевое состояние и все кэшированные представления перечитываются, а запрошенное состояние проверяется по перечитанному списку профилей) и **Notifications** (список ожидающих уведомлений, только чтение). Никаких загрузок профилей, обработки уведомлений и взаимодействия с SM-DP+ — используются только локальные функции ES10a/b/c; для карты не-eUICC подвкладка сообщает об этом.
## SCP81