esim: decode EUICCInfo1/2 and the RAT per SGP.22 (no version bump yet)
The chip endpoint returned pySim's flattened EuiccInfo dict, whose classes are incomplete: the capability fields are raw GreedyBytes, extCardResource is raw bytes and several SGP.22 TLVs are missing from the class, so cards showed 'unknown_ber_tlv_ie_99' and raw hex instead of decoded values. - request the EUICCInfo1/2 and configured-address TLVs raw and decode them in esim.py per SGP.22 v2.6 5.7.8, cross-checked against lpac's es10c_ex.c: extended card resource, UICC/RSP capability bit lists (first octet = unused bits, MSB-first), CI PKI lists, category (both the implicit 0x8B and explicit 0xAB tag encodings), forbidden profile policy rules (0x99), ppVersion (0x04), sasAcreditationNumber (0x0C) and the optional certification data object / TRE fields; undecoded TLVs stay in raw_tlvs instead of being dropped. - add the ES10b GetRat rules authorisation table (PPR ids, allowed operators, consent flag) to the chip response. - PWA: label every new field, map nested labels per path component (the old code only matched whole keys), group the view into EUICCInfo1 / EUICCInfo2 / Addresses / RAT sections, render arrays of objects with index labels and translate the labels (RU). - tests: decoders against a real card's values (077F3E1F80, 0490, 0640, 81010082040006B32C83022646, the RAT fixture) and the frontend label mapping; sw.js simple-v229.
This commit is contained in:
@@ -497,7 +497,7 @@
|
||||
<h3 id="esim" class="text-lg font-medium mb-2">8.8 eSIM / LPA (локальные операции)</h3>
|
||||
<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, прошивка, версия GlobalPlatform, возможности RSP, категория, …) и настроенные адреса SM-DP+ по умолчанию / корневого DS.</li>
|
||||
<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); ничего не обрабатывается и не удаляется.</li>
|
||||
</ul>
|
||||
|
||||
+1
-1
@@ -497,7 +497,7 @@
|
||||
<h3 id="esim" class="text-lg font-medium mb-2">8.8 eSIM / LPA (local operations)</h3>
|
||||
<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 (profile version, SVN, firmware, GlobalPlatform version, RSP capabilities, category, …) and the configured default SM-DP+ / root DS addresses.</li>
|
||||
<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>Notifications</strong> — read-only list of pending notifications (sequence number, operation, address, ICCID); nothing is processed or removed.</li>
|
||||
</ul>
|
||||
|
||||
+82
-11
@@ -1533,13 +1533,25 @@ let _esimNotifications = [];
|
||||
const ESIM_CHIP_LABELS = {
|
||||
eid: 'EID', svn: 'SVN', profile_version: 'Profile version',
|
||||
euicc_firmware_ver: 'Firmware version', ext_card_resource: 'Card resource',
|
||||
installed_application: 'Installed applications',
|
||||
free_non_volatile_memory: 'Free non-volatile memory',
|
||||
free_volatile_memory: 'Free volatile memory',
|
||||
uicc_capability: 'UICC capability', ts102241_version: 'TS 102 241 version',
|
||||
globalplatform_version: 'GlobalPlatform version', rsp_capability: 'RSP capability',
|
||||
euicc_category: 'Category', pp_version: 'PP version',
|
||||
ss_acreditation_number: 'SS accreditation number',
|
||||
euicc_category: 'Category', forbidden_profile_policy_rules: 'Forbidden PPRs',
|
||||
pp_version: 'PP version', ss_acreditation_number: 'SS accreditation number',
|
||||
certification_data_object: 'Certification data',
|
||||
platform_label: 'Platform label', discovery_base_url: 'Discovery base URL',
|
||||
tre_properties: 'TRE properties', tre_product_reference: 'TRE product reference',
|
||||
additional_euicc_profile_package_versions: 'Additional profile package versions',
|
||||
raw_tlvs: 'Raw TLVs',
|
||||
default_dp_address: 'Default SM-DP+ address', root_ds_address: 'Root DS address',
|
||||
euicc_ci_pki_list_for_verification: 'CI PKI (verification)',
|
||||
euicc_ci_pki_list_for_signing: 'CI PKI (signing)',
|
||||
subject_key_identifier: 'Subject key identifier',
|
||||
rat: 'Rules authorisation table', ppr_ids: 'PPR IDs',
|
||||
allowed_operators: 'Allowed operators', ppr_flags: 'PPR flags',
|
||||
plmn: 'PLMN', gid1: 'GID1', gid2: 'GID2',
|
||||
iot_specific_info: 'IoT specific info',
|
||||
};
|
||||
|
||||
@@ -1563,25 +1575,32 @@ function esimGroupEid(eid) {
|
||||
return s.replace(/(.{4})(?=.)/g, '$1 ');
|
||||
}
|
||||
|
||||
// Chip-info value -> [label, value] rows; nested dicts recurse, arrays are
|
||||
// joined, empty values skipped.
|
||||
// Chip-info value -> [label, value] rows; nested dicts recurse, arrays of
|
||||
// scalars are joined, arrays of objects recurse with an index label, empty
|
||||
// values skipped. Labels are mapped and translated per path component.
|
||||
function esimFieldRows(obj, prefix) {
|
||||
const rows = [];
|
||||
if (obj === null || obj === undefined) return rows;
|
||||
if (typeof obj !== 'object') {
|
||||
rows.push([prefix ? esimLabel(prefix) : '', String(obj)]);
|
||||
rows.push([prefix || '', String(obj)]);
|
||||
return rows;
|
||||
}
|
||||
for (const k of Object.keys(obj)) {
|
||||
const v = obj[k];
|
||||
if (v === null || v === undefined || v === '') continue;
|
||||
const label = prefix ? prefix + ' / ' + k : k;
|
||||
const label = prefix ? prefix + ' / ' + t(esimLabel(k)) : t(esimLabel(k));
|
||||
if (Array.isArray(v)) {
|
||||
rows.push([esimLabel(label), v.map(x => (typeof x === 'object' ? JSON.stringify(x) : String(x))).join(', ')]);
|
||||
if (v.length && typeof v[0] === 'object') {
|
||||
v.forEach((item, i) => {
|
||||
rows.push(...esimFieldRows(item, label + ' ' + (i + 1)));
|
||||
});
|
||||
} else {
|
||||
rows.push([label, v.map(x => String(x)).join(', ')]);
|
||||
}
|
||||
} else if (typeof v === 'object') {
|
||||
rows.push(...esimFieldRows(v, label));
|
||||
} else {
|
||||
rows.push([esimLabel(label), String(v)]);
|
||||
rows.push([label, String(v)]);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
@@ -1621,11 +1640,27 @@ function esimRenderChip() {
|
||||
const c = _esimChip || {};
|
||||
const rows = [];
|
||||
if (c.eid) rows.push(['EID', esimGroupEid(c.eid)]);
|
||||
rows.push(...esimFieldRows(c.info1 || {}, ''));
|
||||
rows.push(...esimFieldRows(c.info2 || {}, ''));
|
||||
rows.push(...esimFieldRows(c.addresses || {}, ''));
|
||||
const sections = [
|
||||
['EUICCInfo1', c.info1], ['EUICCInfo2', c.info2],
|
||||
['Addresses', c.addresses], ['Rules authorisation table', c.rat],
|
||||
];
|
||||
for (const [name, data] of sections) {
|
||||
if (!data || (Array.isArray(data) && !data.length)) continue;
|
||||
rows.push(['§ ' + t(name), '']);
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach((item, i) => {
|
||||
rows.push(...esimFieldRows(item, t('Rule') + ' ' + (i + 1)));
|
||||
});
|
||||
} else {
|
||||
rows.push(...esimFieldRows(data, ''));
|
||||
}
|
||||
}
|
||||
let html = '';
|
||||
for (const [k, v] of rows) {
|
||||
if (k.startsWith('§ ')) {
|
||||
html += '<div class="mt-2 mb-0.5 font-semibold text-gray-600 dark:text-slate-300">' + esc(k.slice(2)) + '</div>';
|
||||
continue;
|
||||
}
|
||||
html += '<div class="flex gap-2 mb-0.5"><span class="w-56 shrink-0 text-right text-gray-500 dark:text-slate-400 break-all">' + esc(k) + '</span>' +
|
||||
'<span class="font-mono break-all">' + esc(v) + '</span></div>';
|
||||
}
|
||||
@@ -13573,6 +13608,42 @@ const LANG_RU = {
|
||||
'Not available': 'Недоступно',
|
||||
'No profiles': 'Нет профилей',
|
||||
'No notifications': 'Нет уведомлений',
|
||||
'EID': 'EID',
|
||||
'SVN': 'SVN',
|
||||
'Profile version': 'Версия профиля',
|
||||
'Firmware version': 'Версия прошивки',
|
||||
'Card resource': 'Ресурсы карты',
|
||||
'Installed applications': 'Установленных приложений',
|
||||
'Free non-volatile memory': 'Свободная энергонезависимая память',
|
||||
'Free volatile memory': 'Свободная энергозависимая память',
|
||||
'UICC capability': 'Возможности UICC',
|
||||
'TS 102 241 version': 'Версия TS 102 241',
|
||||
'GlobalPlatform version': 'Версия GlobalPlatform',
|
||||
'RSP capability': 'Возможности RSP',
|
||||
'Category': 'Категория',
|
||||
'Forbidden PPRs': 'Запрещённые PPR',
|
||||
'PP version': 'Версия PP',
|
||||
'SS accreditation number': 'Номер аккредитации SS',
|
||||
'Certification data': 'Данные сертификации',
|
||||
'Platform label': 'Метка платформы',
|
||||
'Discovery base URL': 'Базовый URL Discovery',
|
||||
'TRE properties': 'Свойства TRE',
|
||||
'TRE product reference': 'Ссылка на продукт TRE',
|
||||
'Additional profile package versions': 'Доп. версии пакетов профилей',
|
||||
'Raw TLVs': 'Сырые TLV',
|
||||
'CI PKI (verification)': 'CI PKI (проверка)',
|
||||
'CI PKI (signing)': 'CI PKI (подпись)',
|
||||
'Subject key identifier': 'Идентификатор ключа субъекта',
|
||||
'Rules authorisation table': 'Таблица авторизации правил (RAT)',
|
||||
'Rule': 'Правило',
|
||||
'PPR IDs': 'Идентификаторы PPR',
|
||||
'Allowed operators': 'Разрешённые операторы',
|
||||
'PPR flags': 'Флаги PPR',
|
||||
'PLMN': 'PLMN',
|
||||
'Addresses': 'Адреса',
|
||||
'Default SM-DP+ address': 'Адрес SM-DP+ по умолчанию',
|
||||
'Root DS address': 'Адрес корневого DS',
|
||||
'IoT specific info': 'IoT-специфичная информация',
|
||||
'Enabled': 'Включён',
|
||||
'Disabled': 'Отключён',
|
||||
'Enable': 'Включить',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v228';
|
||||
const CACHE = 'simple-v229';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -42,6 +42,9 @@ test('esimGroupEid groups the hex digits in fours', () => {
|
||||
test('esimLabel maps known chip keys and prettifies the rest', () => {
|
||||
assert.strictEqual(esimLabel('svn'), 'SVN');
|
||||
assert.strictEqual(esimLabel('default_dp_address'), 'Default SM-DP+ address');
|
||||
assert.strictEqual(esimLabel('installed_application'), 'Installed applications');
|
||||
assert.strictEqual(esimLabel('forbidden_profile_policy_rules'), 'Forbidden PPRs');
|
||||
assert.strictEqual(esimLabel('allowed_operators'), 'Allowed operators');
|
||||
assert.strictEqual(esimLabel('some_unknown_key'), 'Some unknown key');
|
||||
});
|
||||
|
||||
@@ -52,12 +55,42 @@ test('esimFieldRows flattens nested values and skips empties', () => {
|
||||
});
|
||||
assert.deepStrictEqual(rows, [
|
||||
['SVN', '1.2.3'],
|
||||
['Nested / profile version', '2.2'],
|
||||
['Nested / Profile version', '2.2'],
|
||||
['List', 'a, b'],
|
||||
['Zero', '0'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('esimFieldRows labels nested keys per path component', () => {
|
||||
const rows = esimFieldRows({
|
||||
euicc_ci_pki_list_for_verification: { subject_key_identifier: '8137AB' },
|
||||
ext_card_resource: {
|
||||
installed_application: 0,
|
||||
free_non_volatile_memory: 439084,
|
||||
},
|
||||
}, '');
|
||||
assert.deepStrictEqual(rows, [
|
||||
['CI PKI (verification) / Subject key identifier', '8137AB'],
|
||||
['Card resource / Installed applications', '0'],
|
||||
['Card resource / Free non-volatile memory', '439084'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('esimFieldRows recurses into arrays of objects with index labels', () => {
|
||||
const rows = esimFieldRows({
|
||||
rat: [{
|
||||
ppr_ids: ['ppr1', 'ppr2'],
|
||||
allowed_operators: [{ plmn: 'EEEEEE', gid1: null, gid2: null }],
|
||||
ppr_flags: ['consentRequired'],
|
||||
}],
|
||||
}, '');
|
||||
assert.deepStrictEqual(rows, [
|
||||
['Rules authorisation table 1 / PPR IDs', 'ppr1, ppr2'],
|
||||
['Rules authorisation table 1 / Allowed operators 1 / PLMN', 'EEEEEE'],
|
||||
['Rules authorisation table 1 / PPR flags', 'consentRequired'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('esimResultText localizes result codes and passes errors through', () => {
|
||||
assert.strictEqual(esimResultText({ result: 'catBusy' }), 'card is busy with a CAT session');
|
||||
assert.strictEqual(esimResultText({ result: 'ok' }), 'ok');
|
||||
|
||||
Reference in New Issue
Block a user