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:
@@ -598,7 +598,7 @@ Values persist on the server until restart. Apply → hex updates; Save → POST
|
||||
|
||||
**Network state monitor** — a compact **Network state** panel next to the simulation buttons shows what the card currently holds and what was last simulated. Its header carries the **simulated service state** (*Undefined* until a scenario or a Location status event sets it, then *Normal service* / *Limited service* / *No service*) with a *PLMN not allowed* marker when the location files or EF.FPLMN show a rejection, plus the current location: PLMN, country and operator, the LAI/RAI/TAI, and the **roaming class** (*Home* when the PLMN equals the HPLMN, *Home equivalent* when it is in EF.EHPLMN, otherwise *Guest*). Below it, one compact line per monitored file (IMSI, EHPLMN, SPDI, HPLMNwAcT, LOCI, PSLOCI, EPSLOCI, EPSNSC, CBMI, CBMIR, SMSstatus, FPLMN) with its decoded summary and how it was last updated (`init`, `write`, `read`, `refresh`); hover for the full decoded fields — long PLMN lists are abbreviated (EF.HPLMNwAcT shows only the first network plus a `… +N` counter). The panel reads the files once at equip (only when the ICCID was readable), updates them in place from the bytes the simulator wrote, re-reads EF.IMSI after every scenario and Location-status event, and never polls the card — **Refresh** re-reads all files on demand.
|
||||
|
||||
**eSIM** — for an eUICC (SGP.22/SGP.32) the **eSIM** pill reads the chip and manages the installed profiles through the local ES10 interface (via pySim, no SM-DP+ contact): **Chip** (EID, EUICCInfo1/2, configured default SM-DP+ / root DS addresses), **Profiles** (state, nickname, provider, ICCID, ISD-P AID, class, owner; **Enable**/**Disable** switches a profile — the card usually sends REFRESH first 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 **Notifications** (read-only pending list). No profile downloads, no notification handling and no SM-DP+ interaction — only the local ES10a/b/c functions are used; a non-eUICC card is reported as such.
|
||||
**eSIM** — for an eUICC (SGP.22/SGP.32) the **eSIM** pill reads the chip and manages the installed profiles through the local ES10 interface (via pySim, no SM-DP+ contact): **Chip** (EID, EUICCInfo1/2 with decoded capability bit lists, card resources, CI PKI identifiers, category, forbidden PPRs, certification data; configured default SM-DP+ / root DS addresses; the rules authorisation table from ES10b GetRat), **Profiles** (state, nickname, provider, ICCID, ISD-P AID, class, owner; **Enable**/**Disable** switches a profile — the card usually sends REFRESH first 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 **Notifications** (read-only pending list). No profile downloads, no notification handling and no SM-DP+ interaction — only the local ES10a/b/c functions are used; a non-eUICC card is reported as such.
|
||||
|
||||
## SCP81
|
||||
|
||||
|
||||
+1
-1
@@ -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, настроенные адреса SM-DP+ / root DS), **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
|
||||
|
||||
|
||||
+41
-3
@@ -180,10 +180,48 @@ eUICC"}`. They use pySim's ES10 static API — no lpac, no SM-DP+ contact, no
|
||||
profile downloads and no notification processing. All run under the card
|
||||
lock; `GET /api/status` reports `euicc` and `eid` for the PWA.
|
||||
|
||||
- `GET /api/esim/chip` — `{"eid": "8904…", "info1": {…}, "info2": {…},
|
||||
- `GET /api/esim/chip` — `{"eid": "8908…", "info1": {…}, "info2": {…},
|
||||
"addresses": {"default_dp_address": …, "root_ds_address": …},
|
||||
"errors": {"<part>": "<reason>"}}` (a part the card does not support is
|
||||
reported in `errors` instead of failing the whole request).
|
||||
"rat": [{…}], "errors": {"<part>": "<reason>"}}` (a part the card does not
|
||||
support is reported in `errors` instead of failing the whole request).
|
||||
The EUICCInfo TLVs are requested raw and decoded per SGP.22 v2.6 §5.7.8
|
||||
(pySim's classes are incomplete), `rat` comes from the ES10b GetRat command
|
||||
(§5.7.13) and undecoded TLVs are preserved in a `raw_tlvs` map:
|
||||
|
||||
```json
|
||||
{"eid": "89086030202200000026000024920451",
|
||||
"info1": {"svn": "2.2.2",
|
||||
"euicc_ci_pki_list_for_verification": ["8137…FB"],
|
||||
"euicc_ci_pki_list_for_signing": ["8137…FB"]},
|
||||
"info2": {"profile_version": "2.3.1", "svn": "2.2.2",
|
||||
"euicc_firmware_ver": "4.2.0",
|
||||
"ext_card_resource": {"installed_application": 0,
|
||||
"free_non_volatile_memory": 439084,
|
||||
"free_volatile_memory": 9798},
|
||||
"uicc_capability": ["usimSupport", "isimSupport", "…"],
|
||||
"ts102241_version": "9.2.0",
|
||||
"globalplatform_version": "2.3.0",
|
||||
"rsp_capability": ["additionalProfile", "testProfileSupport"],
|
||||
"euicc_category": "other",
|
||||
"forbidden_profile_policy_rules": ["ppr1"],
|
||||
"pp_version": "1.0.0",
|
||||
"ss_acreditation_number": "ED-ZI-UP-0826"},
|
||||
"addresses": {"default_dp_address": null,
|
||||
"root_ds_address": "testrootsmds.gsma.com"},
|
||||
"rat": [{"ppr_ids": ["ppr1", "ppr2"],
|
||||
"allowed_operators": [{"plmn": "EEEEEE", "gid1": null, "gid2": null}],
|
||||
"ppr_flags": ["consentRequired"]}],
|
||||
"errors": {}}
|
||||
```
|
||||
|
||||
`uicc_capability`, `rsp_capability`, `forbidden_profile_policy_rules` and
|
||||
`ppr_flags` are ASN.1 BIT STRINGs decoded to the names of the set bits (the
|
||||
first content octet is the unused-bit count, bits are MSB-first);
|
||||
`euicc_category` accepts both the implicit (`0x8B`) and explicit (`0xAB`)
|
||||
tag encodings; `pp_version` (`0x04`) and `ss_acreditation_number` (`0x0C`)
|
||||
are the bare, untagged SGP.22 types; `tre_properties`,
|
||||
`tre_product_reference`, `additional_euicc_profile_package_versions` and
|
||||
`certification_data_object` are decoded when a card sends them.
|
||||
- `GET /api/esim/profiles` — `{"profiles": [{"iccid": "8970…",
|
||||
"isdp_aid": "A000…", "state": "enabled"|"disabled", "nickname": …,
|
||||
"provider": …, "name": …, "class": "test"|"provisioning"|"operational",
|
||||
|
||||
@@ -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');
|
||||
|
||||
+245
-9
@@ -14,7 +14,7 @@ restored afterwards. The caller holds ``_CARD_LOCK``.
|
||||
|
||||
import re
|
||||
|
||||
from osmocom.tlv import flatten_dict_lists
|
||||
from osmocom.tlv import BER_TLV_IE, bertlv_parse_one_rawtag, flatten_dict_lists
|
||||
from pySim.euicc import (
|
||||
AID_ISD_R, CardApplicationISDR, DisableProfileReq, DisableProfileResp,
|
||||
EnableProfileReq, EnableProfileResp, EuiccConfiguredAddresses, EuiccInfo1,
|
||||
@@ -128,20 +128,256 @@ def _error_text(result):
|
||||
return RESULT_MESSAGES.get(result, result)
|
||||
|
||||
|
||||
# ---- SGP.22 EUICCInfo decoding -------------------------------------------
|
||||
#
|
||||
# pySim's EuiccInfo1/2 classes are incomplete (the capability fields are raw
|
||||
# GreedyBytes, several SGP.22 TLVs are missing from the class), so the chip
|
||||
# endpoint requests the raw TLVs and decodes them here, per SGP.22 v2.6
|
||||
# §5.7.8/§5.7.13 and cross-checked against lpac's euicc/es10c_ex.c. Unknown
|
||||
# TLVs are preserved in `raw_tlvs`, never dropped.
|
||||
|
||||
UICC_CAPABILITY_BITS = [
|
||||
'contactlessSupport', 'usimSupport', 'isimSupport', 'csimSupport',
|
||||
'akaMilenage', 'akaCave', 'akaTuak128', 'akaTuak256', 'rfu1', 'rfu2',
|
||||
'gbaAuthenUsim', 'gbaAuthenISim', 'mbmsAuthenUsim', 'eapClient',
|
||||
'javacard', 'multos', 'multipleUsimSupport', 'multipleIsimSupport',
|
||||
'multipleCsimSupport', 'berTlvFileSupport', 'dfLinkSupport', 'catTp',
|
||||
'getIdentity', 'profile-a-x25519', 'profile-b-p256', 'suciCalculatorApi',
|
||||
]
|
||||
RSP_CAPABILITY_BITS = [
|
||||
'additionalProfile', 'crlSupport', 'rpmSupport', 'testProfileSupport',
|
||||
'deviceInfoExtensibilitySupport', 'serviceSpecificDataSupport',
|
||||
]
|
||||
PPR_ID_BITS = ['pprUpdateControl', 'ppr1', 'ppr2', 'ppr3']
|
||||
PPR_FLAG_BITS = ['consentRequired']
|
||||
TRE_PROPERTY_BITS = ['isDiscrete', 'isIntegrated', 'usesRemoteMemory']
|
||||
EUICC_CATEGORIES = {0: 'other', 1: 'basicEuicc', 2: 'mediumEuicc',
|
||||
3: 'contactlessEuicc'}
|
||||
|
||||
|
||||
class _GetRatRequest(BER_TLV_IE, tag=0xbf43):
|
||||
"""ES10b GetRat request (no input data, SGP.22 §5.7.13)."""
|
||||
|
||||
|
||||
def _tlvs(data):
|
||||
"""Walk a BER-TLV buffer -> [(tag, value)]; multi-byte tags kept raw."""
|
||||
out = []
|
||||
rest = bytes(data or b'')
|
||||
while rest:
|
||||
tag, _length, value, rest = bertlv_parse_one_rawtag(rest)
|
||||
out.append((tag, value))
|
||||
return out
|
||||
|
||||
|
||||
def _tlv_value(data, tag):
|
||||
"""Value bytes of the first `tag` TLV in `data` (b'' when absent)."""
|
||||
for t, value in _tlvs(data):
|
||||
if t == tag:
|
||||
return value
|
||||
return b''
|
||||
|
||||
|
||||
def _decode_version(data):
|
||||
"""VersionType: major/minor/revision bytes -> 'M.m.r'."""
|
||||
if len(data) != 3:
|
||||
return None
|
||||
return '%d.%d.%d' % (data[0], data[1], data[2])
|
||||
|
||||
|
||||
def _decode_bit_string(data, names):
|
||||
"""ASN.1 BIT STRING content -> list of set bit names.
|
||||
|
||||
The first octet is the number of unused bits in the final octet; bits are
|
||||
numbered MSB-first within each octet (SGP.22 v2.6 §5.7.8)."""
|
||||
if not data:
|
||||
return []
|
||||
unused = data[0]
|
||||
body = data[1:]
|
||||
out = []
|
||||
for j, byte in enumerate(body):
|
||||
b = byte
|
||||
if j == len(body) - 1 and unused:
|
||||
b &= ~(0xFF >> (8 - unused)) & 0xFF
|
||||
for i in range(8):
|
||||
idx = j * 8 + i
|
||||
if idx >= len(names):
|
||||
break
|
||||
if b & 0x80:
|
||||
out.append(names[idx])
|
||||
b = (b << 1) & 0xFF
|
||||
return out
|
||||
|
||||
|
||||
def _decode_ski_list(data):
|
||||
"""SEQUENCE OF SubjectKeyIdentifier -> hex strings."""
|
||||
return [value.hex().upper() for _tag, value in _tlvs(data)]
|
||||
|
||||
|
||||
def _decode_ext_card_resource(data):
|
||||
"""ETSI TS 102 226 Extended Card Resource Information (inner 81/82/83)."""
|
||||
out = {}
|
||||
raw = {}
|
||||
for tag, value in _tlvs(data):
|
||||
if tag == 0x81:
|
||||
out['installed_application'] = int.from_bytes(value, 'big')
|
||||
elif tag == 0x82:
|
||||
out['free_non_volatile_memory'] = int.from_bytes(value, 'big')
|
||||
elif tag == 0x83:
|
||||
out['free_volatile_memory'] = int.from_bytes(value, 'big')
|
||||
else:
|
||||
raw['%02X' % tag] = value.hex().upper()
|
||||
if raw:
|
||||
out['raw_tlvs'] = raw
|
||||
return out
|
||||
|
||||
|
||||
def _decode_certification_data_object(data):
|
||||
"""CertificationDataObject (SGP.22 v2.6 §5.7.8): platform label + DLOA URL."""
|
||||
out = {}
|
||||
raw = {}
|
||||
for tag, value in _tlvs(data):
|
||||
if tag == 0x80:
|
||||
out['platform_label'] = value.decode('utf-8', 'replace')
|
||||
elif tag == 0x81:
|
||||
out['discovery_base_url'] = value.decode('utf-8', 'replace')
|
||||
else:
|
||||
raw['%02X' % tag] = value.hex().upper()
|
||||
if raw:
|
||||
out['raw_tlvs'] = raw
|
||||
return out
|
||||
|
||||
|
||||
def _decode_info1(raw_hex):
|
||||
"""EUICCInfo1 (BF20): SVN and the CI PKI lists."""
|
||||
out = {'svn': None, 'euicc_ci_pki_list_for_verification': [],
|
||||
'euicc_ci_pki_list_for_signing': []}
|
||||
raw = {}
|
||||
for tag, value in _tlvs(_tlv_value(bytes.fromhex(raw_hex or ''), 0xBF20)):
|
||||
if tag == 0x82:
|
||||
out['svn'] = _decode_version(value)
|
||||
elif tag == 0xA9:
|
||||
out['euicc_ci_pki_list_for_verification'] = _decode_ski_list(value)
|
||||
elif tag == 0xAA:
|
||||
out['euicc_ci_pki_list_for_signing'] = _decode_ski_list(value)
|
||||
else:
|
||||
raw['%02X' % tag] = value.hex().upper()
|
||||
if raw:
|
||||
out['raw_tlvs'] = raw
|
||||
return out
|
||||
|
||||
|
||||
def _decode_info2(raw_hex):
|
||||
"""EUICCInfo2 (BF22) with every SGP.22 v2.6 field decoded."""
|
||||
out = {}
|
||||
raw = {}
|
||||
for tag, value in _tlvs(_tlv_value(bytes.fromhex(raw_hex or ''), 0xBF22)):
|
||||
if tag == 0x81:
|
||||
out['profile_version'] = _decode_version(value)
|
||||
elif tag == 0x82:
|
||||
out['svn'] = _decode_version(value)
|
||||
elif tag == 0x83:
|
||||
out['euicc_firmware_ver'] = _decode_version(value)
|
||||
elif tag == 0x84:
|
||||
out['ext_card_resource'] = _decode_ext_card_resource(value)
|
||||
elif tag == 0x85:
|
||||
out['uicc_capability'] = _decode_bit_string(value, UICC_CAPABILITY_BITS)
|
||||
elif tag == 0x86:
|
||||
out['ts102241_version'] = _decode_version(value)
|
||||
elif tag == 0x87:
|
||||
out['globalplatform_version'] = _decode_version(value)
|
||||
elif tag == 0x88:
|
||||
out['rsp_capability'] = _decode_bit_string(value, RSP_CAPABILITY_BITS)
|
||||
elif tag == 0xA9:
|
||||
out['euicc_ci_pki_list_for_verification'] = _decode_ski_list(value)
|
||||
elif tag == 0xAA:
|
||||
out['euicc_ci_pki_list_for_signing'] = _decode_ski_list(value)
|
||||
elif tag in (0x8B, 0xAB): # implicit and explicit category encodings
|
||||
out['euicc_category'] = EUICC_CATEGORIES.get(
|
||||
int.from_bytes(value, 'big') if value else 0, 'other')
|
||||
elif tag == 0x99:
|
||||
out['forbidden_profile_policy_rules'] = _decode_bit_string(value, PPR_ID_BITS)
|
||||
elif tag == 0x04: # ppVersion has no context tag
|
||||
out['pp_version'] = _decode_version(value)
|
||||
elif tag == 0x0C: # sasAcreditationNumber is a bare UTF8String
|
||||
out['ss_acreditation_number'] = value.decode('utf-8', 'replace')
|
||||
elif tag == 0xAC:
|
||||
out['certification_data_object'] = _decode_certification_data_object(value)
|
||||
elif tag == 0xAD:
|
||||
out['tre_properties'] = _decode_bit_string(value, TRE_PROPERTY_BITS)
|
||||
elif tag == 0xAE:
|
||||
out['tre_product_reference'] = value.decode('utf-8', 'replace')
|
||||
elif tag == 0xAF:
|
||||
out['additional_euicc_profile_package_versions'] = [
|
||||
_decode_version(v) for _t, v in _tlvs(value)]
|
||||
else:
|
||||
raw['%02X' % tag] = value.hex().upper()
|
||||
if raw:
|
||||
out['raw_tlvs'] = raw
|
||||
return out
|
||||
|
||||
|
||||
def _decode_addresses(raw_hex):
|
||||
"""ES10a GetEuiccConfiguredAddresses (BF3C)."""
|
||||
out = {'default_dp_address': None, 'root_ds_address': None}
|
||||
raw = {}
|
||||
for tag, value in _tlvs(_tlv_value(bytes.fromhex(raw_hex or ''), 0xBF3C)):
|
||||
if tag == 0x80:
|
||||
out['default_dp_address'] = value.decode('utf-8', 'replace')
|
||||
elif tag == 0x81:
|
||||
out['root_ds_address'] = value.decode('utf-8', 'replace')
|
||||
else:
|
||||
raw['%02X' % tag] = value.hex().upper()
|
||||
if raw:
|
||||
out['raw_tlvs'] = raw
|
||||
return out
|
||||
|
||||
|
||||
def _decode_rat(raw_hex):
|
||||
"""ES10b GetRat (BF43): the Rules Authorisation Table (SGP.22 §5.7.13)."""
|
||||
out = []
|
||||
table = _tlv_value(_tlv_value(bytes.fromhex(raw_hex or ''), 0xBF43), 0xA0)
|
||||
for _tag, rule in _tlvs(table):
|
||||
entry = {'ppr_ids': [], 'allowed_operators': [], 'ppr_flags': []}
|
||||
for tag, value in _tlvs(rule):
|
||||
if tag == 0x80:
|
||||
entry['ppr_ids'] = _decode_bit_string(value, PPR_ID_BITS)
|
||||
elif tag == 0xA1:
|
||||
operators = []
|
||||
for _t, op in _tlvs(value):
|
||||
ident = {'plmn': None, 'gid1': None, 'gid2': None}
|
||||
for t2, v2 in _tlvs(op):
|
||||
if t2 == 0x80:
|
||||
ident['plmn'] = v2.hex().upper()
|
||||
elif t2 == 0x81:
|
||||
ident['gid1'] = v2.hex().upper()
|
||||
elif t2 == 0x82:
|
||||
ident['gid2'] = v2.hex().upper()
|
||||
operators.append(ident)
|
||||
entry['allowed_operators'] = operators
|
||||
elif tag == 0x82:
|
||||
entry['ppr_flags'] = _decode_bit_string(value, PPR_FLAG_BITS)
|
||||
out.append(entry)
|
||||
return out
|
||||
|
||||
|
||||
def _raw_request(scc, cmd_cls):
|
||||
"""Raw response hex of a request TLV (no pySim response decoding)."""
|
||||
return CardApplicationISDR.store_data_tlv(scc, cmd_cls(), None)
|
||||
|
||||
|
||||
def chip_info(app):
|
||||
"""EID (ES10b GetEuiccData), EUICCInfo1/2 and the configured addresses."""
|
||||
"""EID (ES10c GetEuiccData), EUICCInfo1/2, configured addresses and RAT."""
|
||||
out = {'eid': None, 'info1': None, 'info2': None, 'addresses': None,
|
||||
'errors': {}}
|
||||
'rat': None, 'errors': {}}
|
||||
scc = _select_isdr(app)
|
||||
try:
|
||||
parts = (
|
||||
('eid', lambda: CardApplicationISDR.get_eid(scc)),
|
||||
('info1', lambda: _flatten(CardApplicationISDR.store_data_tlv(
|
||||
scc, EuiccInfo1(), EuiccInfo1))),
|
||||
('info2', lambda: _flatten(CardApplicationISDR.store_data_tlv(
|
||||
scc, EuiccInfo2(), EuiccInfo2))),
|
||||
('addresses', lambda: _flatten(CardApplicationISDR.store_data_tlv(
|
||||
scc, EuiccConfiguredAddresses(), EuiccConfiguredAddresses))),
|
||||
('info1', lambda: _decode_info1(_raw_request(scc, EuiccInfo1))),
|
||||
('info2', lambda: _decode_info2(_raw_request(scc, EuiccInfo2))),
|
||||
('addresses', lambda: _decode_addresses(
|
||||
_raw_request(scc, EuiccConfiguredAddresses))),
|
||||
('rat', lambda: _decode_rat(_raw_request(scc, _GetRatRequest))),
|
||||
)
|
||||
for key, fn in parts:
|
||||
try:
|
||||
|
||||
+111
-8
@@ -12,7 +12,7 @@ from types import SimpleNamespace
|
||||
|
||||
from pySim.euicc import (
|
||||
AID_ISD_R, CardApplicationISDR, DisableProfileResp, DisableResult,
|
||||
EnableProfileResp, EnableResult, EuiccConfiguredAddresses, EuiccInfo1,
|
||||
EnableProfileResp, EnableResult,
|
||||
Iccid, IsdpAid, ListNotificationResp, NotificationAddress,
|
||||
NotificationMetadata, NotificationMetadataList, ProfileClass, ProfileInfo,
|
||||
ProfileInfoListResp, ProfileInfoSeq, ProfileMgmtOperation, ProfileNickname,
|
||||
@@ -23,6 +23,12 @@ from pySim.utils import h2b
|
||||
from pysim_simple_server import esim
|
||||
|
||||
|
||||
def _tlv(tag, value):
|
||||
"""Encode one BER-TLV with a single-byte length (test fixtures only)."""
|
||||
tag_hex = '%02X' % tag if tag <= 0xFF else '%04X' % tag
|
||||
return '%s%02X%s' % (tag_hex, len(value) // 2, value)
|
||||
|
||||
|
||||
class FakeLchan:
|
||||
def __init__(self):
|
||||
self.scc = SimpleNamespace(name='lchan-scc')
|
||||
@@ -148,17 +154,22 @@ class EsimTests(unittest.TestCase):
|
||||
def test_chip_info_collects_parts_and_errors(self):
|
||||
app, _ = make_app()
|
||||
self.patch_eid('89049032000000000000000000000001')
|
||||
info1 = EuiccInfo1()
|
||||
info1.from_tlv(h2b('BF20058203010203'))
|
||||
addresses = EuiccConfiguredAddresses()
|
||||
addresses.from_tlv(h2b('BF3C13800E736D64702E6578616D706C652E6F72678100'))
|
||||
self.patch([info1, RuntimeError('no info2'), addresses])
|
||||
info1 = _tlv(0xBF20, _tlv(0x82, '010203'))
|
||||
addresses = _tlv(0xBF3C, _tlv(0x80, '736D64702E6578616D706C652E6F7267')
|
||||
+ _tlv(0x81, ''))
|
||||
rat = _tlv(0xBF43, _tlv(0xA0, _tlv(0x30, _tlv(0x80, '0460'))))
|
||||
self.patch([info1, RuntimeError('no info2'), addresses, rat])
|
||||
out = esim.chip_info(app)
|
||||
self.assertEqual(out['eid'], '89049032000000000000000000000001')
|
||||
self.assertEqual(out['info1'], {'svn': '1.2.3'})
|
||||
self.assertEqual(out['info1'], {
|
||||
'svn': '1.2.3', 'euicc_ci_pki_list_for_verification': [],
|
||||
'euicc_ci_pki_list_for_signing': []})
|
||||
self.assertIsNone(out['info2'])
|
||||
self.assertIn('info2', out['errors'])
|
||||
self.assertEqual(out['addresses'].get('default_dp_address'), 'smdp.example.o')
|
||||
self.assertEqual(out['addresses'], {'default_dp_address': 'smdp.example.org',
|
||||
'root_ds_address': ''})
|
||||
self.assertEqual(out['rat'], [{'ppr_ids': ['ppr1', 'ppr2'],
|
||||
'allowed_operators': [], 'ppr_flags': []}])
|
||||
|
||||
def test_set_profile_state_enable_uses_iccid_and_refresh(self):
|
||||
app, _ = make_app()
|
||||
@@ -193,6 +204,98 @@ class EsimTests(unittest.TestCase):
|
||||
esim.chip_info(app)
|
||||
|
||||
|
||||
SKI = '81370F5125D0B1D408D4C3B232E6D25E795BEBFB'
|
||||
|
||||
|
||||
class EsimInfoDecodeTests(unittest.TestCase):
|
||||
"""EUICCInfo1/2 and RAT decoders against a real consumer eUICC's values."""
|
||||
|
||||
def test_bit_string_decodes_unused_bits_msb_first(self):
|
||||
self.assertEqual(
|
||||
esim._decode_bit_string(bytes.fromhex('077F3E1F80'),
|
||||
esim.UICC_CAPABILITY_BITS),
|
||||
['usimSupport', 'isimSupport', 'csimSupport', 'akaMilenage',
|
||||
'akaCave', 'akaTuak128', 'akaTuak256', 'gbaAuthenUsim',
|
||||
'gbaAuthenISim', 'mbmsAuthenUsim', 'eapClient', 'javacard',
|
||||
'berTlvFileSupport', 'dfLinkSupport', 'catTp', 'getIdentity',
|
||||
'profile-a-x25519', 'profile-b-p256'])
|
||||
self.assertEqual(
|
||||
esim._decode_bit_string(bytes.fromhex('0490'),
|
||||
esim.RSP_CAPABILITY_BITS),
|
||||
['additionalProfile', 'testProfileSupport'])
|
||||
self.assertEqual(
|
||||
esim._decode_bit_string(bytes.fromhex('0640'), esim.PPR_ID_BITS),
|
||||
['ppr1'])
|
||||
self.assertEqual(esim._decode_bit_string(b'', esim.PPR_ID_BITS), [])
|
||||
|
||||
def test_info2_decodes_every_field(self):
|
||||
ski = _tlv(0x04, SKI)
|
||||
raw = _tlv(0xBF22, ''.join((
|
||||
_tlv(0x81, '020301'), _tlv(0x82, '020202'), _tlv(0x83, '040200'),
|
||||
_tlv(0x84, '81010082040006B32C83022646'),
|
||||
_tlv(0x85, '077F3E1F80'), _tlv(0x86, '090200'),
|
||||
_tlv(0x87, '020300'), _tlv(0x88, '0490'),
|
||||
_tlv(0xA9, ski), _tlv(0xAA, ski), _tlv(0x8B, '00'),
|
||||
_tlv(0x99, '0640'), _tlv(0x04, '010000'),
|
||||
_tlv(0x0C, '45442D5A492D55502D30383236'),
|
||||
)))
|
||||
out = esim._decode_info2(raw)
|
||||
self.assertEqual(out['profile_version'], '2.3.1')
|
||||
self.assertEqual(out['svn'], '2.2.2')
|
||||
self.assertEqual(out['euicc_firmware_ver'], '4.2.0')
|
||||
self.assertEqual(out['ext_card_resource'], {
|
||||
'installed_application': 0, 'free_non_volatile_memory': 439084,
|
||||
'free_volatile_memory': 9798})
|
||||
self.assertEqual(out['uicc_capability'][:3],
|
||||
['usimSupport', 'isimSupport', 'csimSupport'])
|
||||
self.assertEqual(out['ts102241_version'], '9.2.0')
|
||||
self.assertEqual(out['globalplatform_version'], '2.3.0')
|
||||
self.assertEqual(out['rsp_capability'],
|
||||
['additionalProfile', 'testProfileSupport'])
|
||||
self.assertEqual(out['euicc_ci_pki_list_for_verification'], [SKI])
|
||||
self.assertEqual(out['euicc_ci_pki_list_for_signing'], [SKI])
|
||||
self.assertEqual(out['euicc_category'], 'other')
|
||||
self.assertEqual(out['forbidden_profile_policy_rules'], ['ppr1'])
|
||||
self.assertEqual(out['pp_version'], '1.0.0')
|
||||
self.assertEqual(out['ss_acreditation_number'], 'ED-ZI-UP-0826')
|
||||
self.assertNotIn('raw_tlvs', out)
|
||||
|
||||
def test_info2_accepts_both_category_tags_and_keeps_unknown_tlvs(self):
|
||||
out = esim._decode_info2(_tlv(0xBF22, _tlv(0xAB, '02') + _tlv(0xE0, 'AABB')))
|
||||
self.assertEqual(out['euicc_category'], 'mediumEuicc')
|
||||
self.assertEqual(out['raw_tlvs'], {'E0': 'AABB'})
|
||||
|
||||
def test_info2_decodes_certification_data_object(self):
|
||||
out = esim._decode_info2(_tlv(0xBF22, _tlv(0xAC, _tlv(0x80, '504C')
|
||||
+ _tlv(0x81, '68747470733A2F2F642E6578616D706C65'))))
|
||||
self.assertEqual(out['certification_data_object'],
|
||||
{'platform_label': 'PL',
|
||||
'discovery_base_url': 'https://d.example'})
|
||||
|
||||
def test_info1_decodes_svn_and_ski_lists(self):
|
||||
out = esim._decode_info1(_tlv(0xBF20, _tlv(0x82, '020202')
|
||||
+ _tlv(0xA9, _tlv(0x04, SKI))))
|
||||
self.assertEqual(out, {'svn': '2.2.2',
|
||||
'euicc_ci_pki_list_for_verification': [SKI],
|
||||
'euicc_ci_pki_list_for_signing': []})
|
||||
|
||||
def test_rat_decodes_rules(self):
|
||||
raw = _tlv(0xBF43, _tlv(0xA0, _tlv(0x30,
|
||||
_tlv(0x80, '0460')
|
||||
+ _tlv(0xA1, _tlv(0x30, _tlv(0x80, 'EEEEEE')))
|
||||
+ _tlv(0x82, '0180'))))
|
||||
self.assertEqual(esim._decode_rat(raw), [{
|
||||
'ppr_ids': ['ppr1', 'ppr2'],
|
||||
'allowed_operators': [{'plmn': 'EEEEEE', 'gid1': None, 'gid2': None}],
|
||||
'ppr_flags': ['consentRequired']}])
|
||||
|
||||
def test_addresses_decode(self):
|
||||
out = esim._decode_addresses(_tlv(0xBF3C, _tlv(
|
||||
0x81, '74657374726F6F74736D64732E67736D612E636F6D')))
|
||||
self.assertIsNone(out['default_dp_address'])
|
||||
self.assertEqual(out['root_ds_address'], 'testrootsmds.gsma.com')
|
||||
|
||||
|
||||
class EsimRoutingTests(unittest.TestCase):
|
||||
def test_esim_routes_are_in_the_right_http_handlers(self):
|
||||
from pysim_simple_server import server
|
||||
|
||||
Reference in New Issue
Block a user