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:
@@ -498,7 +498,7 @@
|
||||
<p class="text-sm mb-2">Для eUICC (SGP.22/SGP.32) подвкладка <strong>«eSIM»</strong> читает чип и управляет установленными профилями через локальный интерфейс ES10 (через pySim, без обращения к SM-DP+):</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Чип</strong> — EID, EUICCInfo1/2 со всеми декодированными полями (версия профиля, SVN, прошивка, расширенные ресурсы карты — число установленных приложений и свободная энергонезависимая/энергозависимая память —, битовые списки возможностей UICC и RSP, идентификаторы CI PKI, категория, запрещённые PPR, версия PP, номер аккредитации SS, данные сертификации), настроенные адреса SM-DP+ по умолчанию / корневого DS и <strong>таблица авторизации правил</strong> (ES10b GetRat: идентификаторы PPR, разрешённые операторы, флаг согласия). Неизвестные TLV сохраняются как сырой hex.</li>
|
||||
<li><strong>Профили</strong> — все профили с состоянием (включён/отключён), ником, оператором, именем, ICCID, ISD-P AID, классом, владельцем и типом значка. Кнопки <strong>«Включить»</strong>/<strong>«Отключить»</strong> переключают профиль; карта обычно сначала отправляет REFRESH (виден в журнале проактивных команд), а после переключения сессия карты переинициализируется как при equip — ICCID, сетевое состояние и все кэшированные данные перечитываются.</li>
|
||||
<li><strong>Профили</strong> — все профили с состоянием (включён/отключён), ником, оператором, именем, ICCID, ISD-P AID, классом, владельцем и типом значка. Кнопки <strong>«Включить»</strong>/<strong>«Отключить»</strong> переключают профиль; карта обычно сначала отправляет REFRESH (виден в журнале проактивных команд) — терминальный ответ завершает переключение, после чего сессия карты переинициализируется как при equip: ICCID, сетевое состояние и все кэшированные данные перечитываются, а запрошенное состояние проверяется по перечитанному списку профилей.</li>
|
||||
<li><strong>Уведомления</strong> — список ожидающих уведомлений только для чтения (номер последовательности, операция, адрес, ICCID); ничего не обрабатывается и не удаляется.</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">Никаких загрузок профилей, обработки уведомлений и взаимодействия с SM-DP+ — используются только локальные функции ES10a/b/c. Если карта не eUICC, подвкладка сообщает об этом.</p>
|
||||
|
||||
+1
-1
@@ -498,7 +498,7 @@
|
||||
<p class="text-sm mb-2">For an eUICC (SGP.22/SGP.32) the <strong>eSIM</strong> pill reads the chip and manages the installed profiles through the local ES10 interface (via pySim, no SM-DP+ contact):</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Chip</strong> — EID, EUICCInfo1/2 with every field decoded (profile version, SVN, firmware, extended card resource — installed applications and free non-volatile/volatile memory —, UICC and RSP capability bit lists, CI PKI identifiers, category, forbidden profile policy rules, PP version, SS accreditation number, certification data), the configured default SM-DP+ / root DS addresses, and the <strong>rules authorisation table</strong> (ES10b GetRat: PPR IDs, allowed operators, consent flag). Unknown TLVs are preserved as raw hex.</li>
|
||||
<li><strong>Profiles</strong> — every profile with its state (enabled/disabled), nickname, provider, name, ICCID, ISD-P AID, class, owner and icon type. <strong>Enable</strong>/<strong>Disable</strong> switches a profile; the card usually sends REFRESH first (logged in the proactive command log) and after the switch the card session is re-initialized like an equip, so the ICCID, network state and every cached card view are re-read.</li>
|
||||
<li><strong>Profiles</strong> — every profile with its state (enabled/disabled), nickname, provider, name, ICCID, ISD-P AID, class, owner and icon type. <strong>Enable</strong>/<strong>Disable</strong> switches a profile; the card usually sends REFRESH first (logged in the proactive command log) — the terminal response completes the switch and the card session is then re-initialized like an equip, so the ICCID, network state and every cached card view are re-read, and the requested state is verified against the re-read profile list.</li>
|
||||
<li><strong>Notifications</strong> — read-only list of pending notifications (sequence number, operation, address, ICCID); nothing is processed or removed.</li>
|
||||
</ul>
|
||||
<p class="text-sm mb-3">No profile downloads, no notification handling and no SM-DP+ interaction — only the local ES10a/b/c functions are used. The card must be an eUICC; otherwise the pill says so.</p>
|
||||
|
||||
+16
-7
@@ -1612,6 +1612,20 @@ function esimResultText(res) {
|
||||
return t(ESIM_RESULT_KEYS[res.result] || res.message || res.result || 'Error');
|
||||
}
|
||||
|
||||
// Status text after an Enable/Disable switch: the card result, the re-init and
|
||||
// the verified resulting profile state.
|
||||
function esimSwitchStatus(res) {
|
||||
if (!res || !res.ok) return t('Profile switch failed') + ': ' + esimResultText(res);
|
||||
let msg = t('Profile switched');
|
||||
if (res.reinitialized) msg += ' — ' + t('card re-initialized');
|
||||
if (res.refresh_seen) msg += ' (' + t('REFRESH received') + ')';
|
||||
if (res.verified === false) {
|
||||
msg += ' — ' + t('not confirmed');
|
||||
if (res.state_after) msg += ' (' + esimStateLabel(res.state_after) + ')';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
function esimStateLabel(state) {
|
||||
if (state === 'enabled') return t('Enabled');
|
||||
if (state === 'disabled') return t('Disabled');
|
||||
@@ -1757,13 +1771,7 @@ async function esimSetProfile(iccid, isdpAid, action) {
|
||||
if (iccid) body.iccid = iccid;
|
||||
if (isdpAid) body.isdp_aid = isdpAid;
|
||||
const res = await pysimFetch('/api/esim/profile', body);
|
||||
if (res && res.ok) {
|
||||
if (statusEl) statusEl.textContent = t('Profile switched') +
|
||||
(res.reinitialized ? ' — ' + t('card re-initialized') : '') +
|
||||
(res.refresh_seen ? ' (' + t('REFRESH received') + ')' : '');
|
||||
} else if (statusEl) {
|
||||
statusEl.textContent = t('Profile switch failed') + ': ' + esimResultText(res);
|
||||
}
|
||||
if (statusEl) statusEl.textContent = esimSwitchStatus(res);
|
||||
await esimFetchAll();
|
||||
} catch (e) {
|
||||
if (statusEl) statusEl.textContent = t('Error') + ': ' + e.message;
|
||||
@@ -13687,6 +13695,7 @@ const LANG_RU = {
|
||||
'Profile switched': 'Профиль переключён',
|
||||
'card re-initialized': 'карта переинициализирована',
|
||||
'REFRESH received': 'получен REFRESH',
|
||||
'not confirmed': 'не подтверждено',
|
||||
'Profile switch failed': 'Не удалось переключить профиль',
|
||||
'No commands logged yet.': 'Команды пока не регистрировались.',
|
||||
'Send event notification to the card?': 'Отправить уведомление о событии на карту?',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v229';
|
||||
const CACHE = 'simple-v230';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -23,7 +23,7 @@ function extractFunc(src, name) {
|
||||
|
||||
let code = '';
|
||||
for (const fn of ['esimLabel', 'esimGroupEid', 'esimFieldRows', 'esimResultText',
|
||||
'esimStateLabel', 'esimOperationsText', 'esimProfileRows']) {
|
||||
'esimStateLabel', 'esimOperationsText', 'esimProfileRows', 'esimSwitchStatus']) {
|
||||
code += extractFunc(html, fn) + '\n';
|
||||
}
|
||||
for (const c of ['ESIM_CHIP_LABELS', 'ESIM_RESULT_KEYS']) {
|
||||
@@ -98,6 +98,20 @@ test('esimResultText localizes result codes and passes errors through', () => {
|
||||
assert.strictEqual(esimResultText(null), '');
|
||||
});
|
||||
|
||||
test('esimSwitchStatus reports the switch, re-init, REFRESH and verification', () => {
|
||||
assert.strictEqual(esimSwitchStatus({ ok: true }), 'Profile switched');
|
||||
assert.strictEqual(
|
||||
esimSwitchStatus({ ok: true, reinitialized: true, refresh_seen: true }),
|
||||
'Profile switched — card re-initialized (REFRESH received)');
|
||||
assert.strictEqual(
|
||||
esimSwitchStatus({ ok: true, reinitialized: true, verified: false, state_after: 'disabled' }),
|
||||
'Profile switched — card re-initialized — not confirmed (Disabled)');
|
||||
assert.strictEqual(esimSwitchStatus({ ok: false, result: 'catBusy' }),
|
||||
'Profile switch failed: card is busy with a CAT session');
|
||||
assert.strictEqual(esimSwitchStatus({ ok: false, error: 'SW 6985' }),
|
||||
'Profile switch failed: SW 6985');
|
||||
});
|
||||
|
||||
test('esimStateLabel and esimOperationsText map the profile metadata', () => {
|
||||
assert.strictEqual(esimStateLabel('enabled'), 'Enabled');
|
||||
assert.strictEqual(esimStateLabel('disabled'), 'Disabled');
|
||||
|
||||
Reference in New Issue
Block a user