Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 194b88e7d1 | |||
| d5e67360fe | |||
| 87bf4acbbc | |||
| 180dd50c06 | |||
| 101ef21e9f | |||
| d036ef79a1 | |||
| 9411fd775d | |||
| ded63c94e4 | |||
| 1174a340db |
+100
-11
@@ -9,6 +9,101 @@ every change carries a descriptive commit message.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.35.0] — 2026-10-23
|
||||
|
||||
### Added
|
||||
|
||||
- **EF.SMSP decoded editor** — the full SMS-parameter form (TS 31.102 §4.2.27): alpha,
|
||||
TP-Destination Address and TS-Service Centre Address (each with its own length
|
||||
convention and a TON select; an empty SCA means the default SMSC), TP-PID, TP-DCS and
|
||||
the relative TP-VP — every parameter with an *absent (the ME default applies)* toggle.
|
||||
Live hints decode the DCS (TS 23.038 §4: coding group, alphabet, class, compression,
|
||||
message-waiting) and the relative validity period (TS 23.040 §9.2.3.12.1). Unchanged
|
||||
parameters keep their exact bytes (a card quirk such as a DCS marked absent with byte
|
||||
`00` survives), alphanumeric TON addresses are preserved read-only, and all checked
|
||||
records are validated before anything is written.
|
||||
- **Edit decoded, wave 2** — new editors for the service tables **EF.UST / EF.IST /
|
||||
EF.EST** (a filterable bit grid preserving services the table does not model), the
|
||||
scalars **EF.ACC**, **EF.PHASE**, **EF.HPPLMN** and **EF.AD**, the PLMN lists
|
||||
**EF.PLMNsel / EF.FPLMN / EF.EHPLMN**, the identities **EF.ICCID / EF.IMSI** (behind a
|
||||
confirmation — writing them changes the card identity) and the cell-broadcast lists
|
||||
**EF.CBMI / EF.CBMID / EF.CBMIR**. **EF.SST** is deliberately left read-only: its
|
||||
SIM layout is 2 bits per service (allocated + activated), which needs its own editor.
|
||||
- The file-format survey (`docs/decoded-editing.md`) now records the shipped waves and
|
||||
marks **EF.DIR / EF.ARR / EF.SMS / EF.PBR** as postponed by decision (read-only).
|
||||
|
||||
### Fixed
|
||||
|
||||
- `efDecSmsp` mapped the parameter indicators shifted (SCA on bit 5, PID on bit 2, DCS
|
||||
on bit 3, VP on bit 4); the spec order is DA/SCA/PID/DCS/VP on bits 1–5 with bits 6–8
|
||||
reserved — a record with a mixed indicator (e.g. `E9`) showed DCS and hid VP.
|
||||
- The read-only EF.SMSP view decoded the TP-DA length as octets, although it is the digit
|
||||
count (TS 23.040 §9.1.2.5); a slot padded with `00` gained phantom digits.
|
||||
- The **Read decoded** pill no longer looks disabled when it is enabled: an inactive-but-
|
||||
enabled pill is now an outlined button (blue on white/slate), and the grey fill and
|
||||
dimming are left to the actually disabled state.
|
||||
- **EF.ACC** class numbering (TS 31.102 4.2.15): byte 1 b8…b1 codes classes 15…8 and
|
||||
byte 2 classes 7…0 — the decoder counted class 0 at the MSB (an all-FF ACC read as
|
||||
class 0 instead of classes 0–15).
|
||||
- **EF.AD** (TS 31.102 4.2.18): the operation mode is a whole-byte enum
|
||||
(00/80/01/81/02/04), not bits 0-1 plus a test bit; the MNC length may be 0 (service
|
||||
n°130 available) or absent (3-byte SIM form) — the editor previously refused both.
|
||||
- **EF.SST** (TS 51.011 10.3.7): the SIM service table is decoded with its 2-bits-per-
|
||||
service layout (allocated + activated) instead of the UST 1-bit codec.
|
||||
- **EF.IMSI**: the parity/type nibble is `9` for an odd digit count and `1` for an even
|
||||
one (TS 24.008 10.5.1.4); the encoder always wrote `9`.
|
||||
- Saving a PLMN/CB list with no edits no longer rewrites it as all-FF (the collected rows
|
||||
and the list encoders disagreed on the value shape).
|
||||
|
||||
## [3.33.0] — 2026-10-23
|
||||
|
||||
### Added
|
||||
|
||||
- **Edit decoded** in the file manager: a file is read raw, decoded into its own editable
|
||||
form, re-encoded in the browser and written through the normal update path. The first
|
||||
wave covers **EF.SPN**, **EF.PLMNwAcT / EF.OPLMNwAcT / EF.HPLMNwAcT**, the dialling-number
|
||||
files **EF.ADN / EF.FDN / EF.SDN / EF.MSISDN / EF.LND / EF.BDN** and **EF.PNN**. Shapes:
|
||||
a flat transparent form (SPN), a transparent list editor with add/remove rows (PLMN+ACT),
|
||||
per-record forms with a selection checkbox that is auto-checked when a field changes
|
||||
(dialling numbers, PNN) and per-record TLV item rows with add/remove/edit (PNN's full and
|
||||
short names plus the PLMN additional information). All checked records are encoded and
|
||||
validated before anything is written; unchanged fields, record lengths, extension
|
||||
pointers (EF.EXT1–EXT5), the BDN comparison-method byte and unknown PNN TLVs are
|
||||
preserved, and an edit that would break a reference (a number continued in an extension
|
||||
file, a dropped mandatory PNN full name) is refused.
|
||||
- `docs/decoded-editing.md`: the file-format survey behind the design (every known EF
|
||||
classified by structure/coding into the editor's shapes S0–S10, with the release waves).
|
||||
|
||||
### Removed
|
||||
|
||||
- The file manager's decoded view no longer carries the collapsed **pySim JSON (server)**
|
||||
disclosure — the view is the client-side EF decoder's table only. The disclosure was an
|
||||
unrelated *second* decode: opening it issued another `/api/read` with `mode: 'decoded'`
|
||||
to run pySim's `read_binary_decoded`/`read_records_decoded`, and it was reachable only
|
||||
when our decoder covered the file (`Read decoded` is decoder-gated), so the one case
|
||||
where a second opinion would matter — a file our decoders do not cover — never offered
|
||||
it. `/api/read` keeps `mode: 'decoded'` for API callers; the help, READMEs and project
|
||||
notes say the decoded table is built in the browser from the same raw bytes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **EF.BDN** decodes and encodes with its own `X+15` layout (the trailing Comparison Method
|
||||
Pointer); the shared ADN codec had treated it as `X+14`.
|
||||
- `efPlmnAct` decoded the EC-GSM-IoT-only ACT (`b8=1,b4=1,b3=0`, `0x0088`) as nothing while
|
||||
checking the impossible `0x0086` (TS 31.102 Table 4.2.5.2).
|
||||
- PNN network names: the decoder now trusts a non-zero spare-bit count before the
|
||||
"all bytes < 0x80 → uncompressed" heuristic, so a valid packed name can no longer be
|
||||
misread (and later written back wrong).
|
||||
- `/api/write` and `/api/read` report a cmd2 exception in the captured output as a failure
|
||||
instead of `success: true` with `SW: 9000`.
|
||||
- The empty amber **preset-mismatch banner** no longer flashes on the SCP80/RAM view.
|
||||
`ramOpChanged()` (v3.28.0) un-hid `#ram-preset-warning` by the operation kind, but the
|
||||
banner's text *and* visibility belong to `spPresetWarningRender()` (which also handles
|
||||
the batch suppression): with no warning text the empty bordered box stayed visible until
|
||||
the next status poll re-hid it — on every RAM view and operation switch. The visibility
|
||||
is content-driven again; `frontend/tests/ram.test.js` pins it (the pre-fix code fails
|
||||
the test).
|
||||
|
||||
## [3.32.5] — 2026-10-21
|
||||
|
||||
### Fixed
|
||||
@@ -25,6 +120,11 @@ every change carries a descriptive commit message.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The auto-equip's captured traceback now always includes the **exception line**: cmd2
|
||||
renders a rich-box traceback longer than the logged head, so the actual error was cut
|
||||
off. The tail is appended after a `...` marker; the "releasing the old link failed"
|
||||
message now says it continues with a fresh link (a removed card's dead PC/SC handle
|
||||
always fails to disconnect).
|
||||
- A card insertion now marks the PC/SC link for a rebuild, so the **first** auto-equip
|
||||
attempt already uses a fresh link. On Windows the old link cannot connect after a
|
||||
swap — v3.32.2 recovered it, but only on the second attempt, after a failed init, a
|
||||
@@ -33,17 +133,6 @@ every change carries a descriptive commit message.
|
||||
equip worker (never in the pyscard monitor thread) and `_ensure_transport` releases
|
||||
the old link first.
|
||||
|
||||
## [3.32.3] — 2026-10-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- The auto-equip's captured traceback now always includes the **exception line**: cmd2
|
||||
renders a rich-box traceback longer than the logged head, so the actual error was cut
|
||||
off (seen in the first Windows confirmation of v3.32.2). The tail is appended after a
|
||||
`...` marker.
|
||||
- The "releasing the old link failed" message now says it continues with a fresh link —
|
||||
a removed card's dead PC/SC handle always fails to disconnect.
|
||||
|
||||
## [3.32.2] — 2026-10-21
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -593,8 +593,9 @@ Connects to the bundled [`pysim-simple-server`](pysim_simple_server/) for live c
|
||||
Browse the UICC filesystem in a tree view. Files are shown with names, FIDs, and AIDs (for ADFs). Selecting a file shows its detail pane: FID, file type, size / record layout and the decoded FCI (in a bordered block labelled with the file's symbolic name) above the content pane. When the FCI references its access rules through an EF.ARR record (security attribute `8B`), the referenced record's decoded rules are shown as indented lines under that row — resolved from the card's cached EF.ARR copies (MF, DF.TELECOM/DF.GSM and ADF.USIM, following the file's DF chain; a snapshot resolves them from its own captured copy).
|
||||
|
||||
- Entries are grouped with DFs above EFs and sorted by **FID** or symbolic **Name** (pills pinned above the tree together with **Probe all files**, remembered in `localStorage`)
|
||||
- **Read raw** / **Read decoded** — read the selected file as a hex dump or as a decoded field table (client-side decoders for IMSI, ICCID, SPN, PLMN lists, LOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the highlighted pill is the current view and clicking either pill (re-)reads the file. The server-side pySim JSON of the same read stays behind a collapsed *pySim JSON (server)* disclosure; **Read decoded** is disabled when the client-side decoders do not cover the file (resolved by name/FID before reading)
|
||||
- **Edit raw** — edit the raw hex data and **Save** to write back (or **Cancel**); transparent files use a textarea, record files one input per record with selection checkboxes. The decoded view is read-only — **Edit raw** switches to the hex view first and reads the file if needed
|
||||
- **Read raw** / **Read decoded** — read the selected file as a hex dump or as a decoded field table (client-side decoders for IMSI, ICCID, SPN, PLMN lists, LOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the highlighted pill is the current view and clicking either pill (re-)reads the file. The decoded table is built in the browser from the same raw bytes (no server-side decode); **Read decoded** is disabled when the client-side decoders do not cover the file (resolved by name/FID before reading)
|
||||
- **Edit raw** — edit the raw hex data and **Save** to write back (or **Cancel**); transparent files use a textarea, record files one input per record with selection checkboxes. **Edit raw** switches to the hex view first and reads the file if needed
|
||||
- **Edit decoded** — edit through the file's own decoded form (client-side encoders; written via the normal update path): **EF.SPN**, **EF.PNN**, the PLMN lists **PLMNsel / FPLMN / EHPLMN** and **PLMNwAcT / OPLMNwAcT / HPLMNwAcT**, the dialling-number files **ADN / FDN / SDN / MSISDN / LND / BDN**, the service tables **UST / IST / EST** (**SST** stays read-only until the SIM allocated/activated editor exists), **ACC**, **PHASE**, **HPPLMN**, **AD**, **ICCID / IMSI** (behind a confirmation), the cell-broadcast lists **CBMI / CBMID / CBMIR**, and **EF.SMSP** (the full SMS-parameter form: alpha, TP-DA/TS-SCA with their addressing rules and TON, TP-PID, TP-DCS and relative TP-VP, each with an absent/default toggle and live DCS/validity hints). Transparent files get one form (PLMN rows and PNN items with add/remove); record files get one form per record with a checkbox that is auto-checked when a field changes, so only edited records are written. **Save** validates every selected record before writing; extension pointers (EF.EXT1–EF.EXT5) and the BDN comparison-method byte are preserved, and editing a number continued in an extension file is refused. The shape survey and the planned coverage are in `docs/decoded-editing.md`
|
||||
- **Probe all files** — walks the whole tree (incl. custom files), marks every entry present/absent with *N / total* progress, stoppable, and ends with a summary; browsing itself stays lazy. Missing files are shown in red (✗); a present but empty DF shows `(empty)`
|
||||
- **ADM** — files that need the administrator PIN fail with `6982`/`9804`; when the matching card preset (same ICCID) carries an ADM key, a **Verify ADM** button appears next to the error and the header badge (`ADM ✓/✗ ⚿`) becomes clickable. Every wrong key consumes an attempt (the remaining attempts are shown and a retry asks for confirmation); a blocked ADM needs the card's unblock key
|
||||
|
||||
|
||||
+3
-2
@@ -568,8 +568,9 @@ Delivery PoR (SPI2 `01`) проще — карта возвращает PoR на
|
||||
Дерево файлов UICC. Отображаются имена, FID и AID (для ADF). При выборе файла открывается панель деталей: FID, тип файла, размер / структура записей и декодированный FCI (в блоке с рамкой и именем файла) над панелью содержимого. Когда FCI ссылается на свои правила доступа через запись EF.ARR (атрибут безопасности `8B`), правила указанной записи показываются отступом под этой строкой — они берутся из кэшированных копий EF.ARR карты (MF, DF.TELECOM/DF.GSM и ADF.USIM, по цепочке DF файла; снимок разрешает их из собственной захваченной копии).
|
||||
|
||||
- Элементы сгруппированы (DF выше EF) и отсортированы по **FID** или символьному **имени** (пиллы закреплены над деревом вместе с **«Проверить все файлы»**, выбор сохраняется в `localStorage`)
|
||||
- **Read raw** / **Read decoded** — чтение выбранного файла как hex-дампа или как таблицы декодированных полей (клиентские декодеры: IMSI, ICCID, SPN, списки PLMN, LOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); подсвеченная пилла — текущий вид, клик по любой из них перечитывает файл. Серверный pySim JSON того же чтения остаётся в свёрнутом блоке *pySim JSON (server)*; **Read decoded** отключается, когда клиентские декодеры не покрывают файл (определяется по имени/FID до чтения)
|
||||
- **Edit raw** — редактирование сырых hex-данных и **Save** для записи (или **Cancel**); для transparent-файлов — textarea, для record-файлов — по полю на запись с флажками выбора. Декодированный вид только для чтения — **Edit raw** сначала переключается на hex и при необходимости читает файл
|
||||
- **Read raw** / **Read decoded** — чтение выбранного файла как hex-дампа или как таблицы декодированных полей (клиентские декодеры: IMSI, ICCID, SPN, списки PLMN, LOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); подсвеченная пилла — текущий вид, клик по любой из них перечитывает файл. таблица декодируется в браузере из тех же сырых байт (без серверного декодирования); **Read decoded** отключается, когда клиентские декодеры не покрывают файл (определяется по имени/FID до чтения)
|
||||
- **Edit raw** — редактирование сырых hex-данных и **Save** для записи (или **Cancel**); для transparent-файлов — textarea, для record-файлов — по полю на запись с флажками выбора. **Edit raw** сначала переключается на hex и при необходимости читает файл
|
||||
- **Edit decoded** — редактирование в собственной декодированной форме файла (клиентские кодировщики; запись обычным путём): **EF.SPN**, **EF.PNN**, списки PLMN **PLMNsel / FPLMN / EHPLMN** и **PLMNwAcT / OPLMNwAcT / HPLMNwAcT**, файлы номеров **ADN / FDN / SDN / MSISDN / LND / BDN**, таблицы сервисов **UST / IST / EST** (**SST** только для чтения до появления редактора allocated/activated), **ACC**, **PHASE**, **HPPLMN**, **AD**, **ICCID / IMSI** (с подтверждением) списки cell broadcast **CBMI / CBMID / CBMIR** и **EF.SMSP** (полная форма SMS-параметров: alpha, TP-DA/TS-SCA с их правилами адресации и TON, TP-PID, TP-DCS и относительный TP-VP; у каждого — флажок «отсутствует/по умолчанию» и живые подсказки по DCS и сроку действия). Для transparent-файла — одна форма (строки PLMN и элементы PNN с добавлением/удалением); для record-файла — форма на запись с флажком, который ставится автоматически при изменении поля, поэтому записываются только изменённые записи. **Save** проверяет все выбранные записи до записи; указатели расширений (EF.EXT1–EF.EXT5) и байт метода сравнения BDN сохраняются; редактирование номера, продолжение которого лежит в файле расширения, отклоняется. Обзор форматов и план охвата — в `docs/decoded-editing.md`
|
||||
- **Проверить все файлы** — обход всего дерева (включая пользовательские) с пометкой «есть/нет», прогрессом *N / всего*, возможностью остановки и сводкой в конце; сам просмотр остаётся ленивым. Отсутствующие файлы показаны красным (✗); существующий пустой DF — `(пусто)`
|
||||
- **ADM** — файлы, требующие администраторский PIN, возвращают `6982`/`9804`; если в подходящем пресете карты (тот же ICCID) есть ключ ADM, рядом с ошибкой появляется кнопка **«Проверить ADM»**, а значок в заголовке (`ADM ✓/✗ ⚿`) становится кликабельным. Каждый неверный ключ расходует попытку (остаток показывается, повторная попытка требует подтверждения); заблокированный ADM требует ключа разблокировки карты
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
# Decoded editing — file-format survey and design
|
||||
|
||||
Status: design note for the **Edit decoded** feature. Waves 1–3 shipped in
|
||||
v3.33.0 (SPN/PLMN/ADN/PNN), v3.34.0 (service tables/scalars/PLMN lists/BCD/CB)
|
||||
and v3.35.0 (EF.SMSP); decoded editing for EF.DIR/EF.ARR/EF.SMS/EF.PBR is
|
||||
**postponed by decision** (their decoded view is enough). It classifies
|
||||
the EFs the workbench knows (the client-side decoder registry) by the spec's
|
||||
own structure and content coding, assigns each an editing *shape*, and sets the
|
||||
release waves. The reference is the pinned specs (`projects/docs/uicc/`):
|
||||
3GPP TS 31.102 v18.10 (USIM), TS 51.011 v4.15 (SIM), TS 24.008 v18.8 (network
|
||||
name / PLMN / address coding), TS 102 221 (file structure / Annex A text).
|
||||
pySim is a cross-check only.
|
||||
|
||||
The editor is client-side, like the decoders: the file is read raw, decoded
|
||||
into its schema's values, edited, re-encoded and written with `/api/write`.
|
||||
Only the files listed with a shape have an encoder; everything else keeps the
|
||||
raw editor.
|
||||
|
||||
## 1. Editing shapes
|
||||
|
||||
Each shape is one renderer + one encoder contract. `fields` are the flat form
|
||||
values, `list` is a transparent file holding N items, `items` is a record
|
||||
holding a TLV list; the record-scoped variants reuse the same field/item
|
||||
renderers inside a per-record block with a "write this record" checkbox
|
||||
(auto-checked when an input changes).
|
||||
|
||||
| Shape | File / record content | Editor |
|
||||
|---|---|---|
|
||||
| **S0** read-only | card- or network-managed values (location, keys, counters) | no editor; the button says why |
|
||||
| **S1** scalar/flag TR | one or a few bytes / flags | one form |
|
||||
| **S2** text TR | Annex A (TS 102 221) or NAI text | one form, text field |
|
||||
| **S3** BCD TR | ICCID / IMSI (nibble-swapped BCD, parity/type nibble) | one form, digits |
|
||||
| **S4** PLMN list TR | n × 3-byte PLMN (TS 24.008) | item rows, add/remove |
|
||||
| **S5** PLMN+ACT list TR | n × (3-byte PLMN + 2-byte ACT) | item rows, add/remove, tech multi-select |
|
||||
| **S6** bit/service list TR | service or identifier bit lists | item list with labels (checkbox per service/id) |
|
||||
| **S7** fixed-record fields | alpha + BCD address + ids (ADN family), LAI+id (OPL), code+alpha+category (ECC), TP parameters (SMSP) | per-record form + checkbox |
|
||||
| **S8** record TLV list | PNN (name TLVs), DIR (application templates), ARR (access rules), PBR (A8/A9/AA containers) | per-record item rows, add/remove/edit; unknown TLVs preserved |
|
||||
| **S9** nested structures | PBR containers, DIR templates, EXT chains | later: a tree, or read-only with a structured view |
|
||||
| **S10** dedicated builders | SMS (TPDU), SMSP (TPDU parameter set), key material | later: a dedicated form, not a generic encoder |
|
||||
|
||||
Rules that apply to every encoder (and are what the tests pin):
|
||||
|
||||
- encode **only the edited values**, from the original record/file bytes:
|
||||
length, padding and anything the schema does not expose are preserved;
|
||||
- never change the file/record length; refuse when the new value does not fit;
|
||||
- preserve the original text coding when the text is unchanged, and choose a
|
||||
spec-legal coding when it changed (Annex A: unpacked GSM 7-bit / UCS-2; PNN
|
||||
network name: packed GSM 7-bit with the spare-bit count / UCS-2);
|
||||
- refuse (not silently truncate) when an edit would invalidate a cross-file
|
||||
reference (`EF.EXT1` chain, PBR/IAP, OPL→PNN record id, CMI pointer).
|
||||
|
||||
## 2. Survey of the known files
|
||||
|
||||
`Wave` is the release that adds the encoder. Files we do not decode yet are
|
||||
listed only where they matter for the plan; the spec's ~120 USIM EFs include
|
||||
many specialized ones (MMS, VGCS/VBS, ePDG, IMS configuration, …) that are not
|
||||
editing candidates.
|
||||
|
||||
### 2.1 Transparent files
|
||||
|
||||
| EF | FID | Spec | Size | Content | Shape | Wave |
|
||||
|---|---|---|---|---|---|---|
|
||||
| EF.ICCID | 2FE2 | TS 51.011 10.3.1 | 10 B | BCD (nibble-swapped), F padding; UPDATE is NEV per TS 102 221 | S3 | **3.34** |
|
||||
| EF.IMSI | 6F07 | TS 31.102 4.2.2 | 9 B | length byte + parity/type nibble ('9' odd / '1' even) + BCD | S3 | **3.34** |
|
||||
| EF.IST | 6F07 | TS 31.102 4.2.2 (SAIP) | 1–2 B | service bits | S6 | **3.34** |
|
||||
| EF.UST | 6F38 | TS 31.102 4.2.8 | X ≥ 1 | service bits | S6 | **3.34** |
|
||||
| EF.SST | 6F38 | TS 51.011 10.3.7 | X ≥ 1 | **2 bits/service** (allocated + activated) | S6 (pending) | decoder fixed; editor later |
|
||||
| EF.EST | 6F56 | TS 31.102 4.2.47 | X ≥ 1 | enabled-service bits (3 services) | S6 | **3.34** |
|
||||
| EF.LI | 6F05 | TS 31.102 4.2.1 | 2n | ISO 639 letters (2 B each) | S6 | 3.35 |
|
||||
| EF.LP / EF.PL | 6F05 / 2F05 | TS 51.011 10.3.1 / 10.1.2 | 1–n | **1 byte per language** (DCS language coding) | S6 | 3.35 |
|
||||
| EF.SPN | 6F46 | TS 31.102 4.2.12 | 17 B | condition byte + Annex A text | S2 | **3.33** |
|
||||
| EF.PLMNsel | 6F30 | TS 51.011 10.3.4 | 3n (n ≥ 8) | PLMN list | S4 | **3.34** |
|
||||
| EF.FPLMN | 6F7B | TS 31.102 4.2.16 | 3n (n ≥ 4) | PLMN list (FFFFFF holes anywhere) | S4 | **3.34** |
|
||||
| EF.EHPLMN | 6FD9 | TS 31.102 4.2.84 | 3n | PLMN list | S4 | **3.34** |
|
||||
| EF.PLMNwAcT | 6F60 | TS 31.102 4.2.5 | 5n (n ≥ 8) | PLMN + ACT | S5 | **3.33** |
|
||||
| EF.OPLMNwAcT | 6F61 | TS 31.102 4.2.53 | 5n (n ≥ 8) | PLMN + ACT | S5 | **3.33** |
|
||||
| EF.HPLMNwAcT | 6F62 | TS 31.102 4.2.54 | 5n (n ≥ 1) | PLMN + ACT | S5 | **3.33** |
|
||||
| EF.SPDI | 6FCD | TS 31.102 4.2.66 | x | TLV: A3 > 80 > PLMN list | S4 | 3.35 |
|
||||
| EF.GID1 / GID2 | 6F3E / 6F3F | TS 31.102 4.2.10/4.2.11 | n | identifier bytes (coding not stated in the clause) | S2 | 3.35 (verify TS 31.101) |
|
||||
| EF.SUUME | 6F54 | TS 51.011 10.3.34 | X+Y | alpha (Annex A) | S2 | 3.35 |
|
||||
| EF.IMPI/DOMAIN/IMPU | 6F02/6F03/6F04 | TS 31.102 (ISIM) | X | NAI text | S2 | 3.35 |
|
||||
| EF.ACC | 6F78 | TS 31.102 4.2.15 | 2 B | class bits (b8…b1 = 15…8 / 7…0; ACC 10 written 0) | S6 | **3.34** |
|
||||
| EF.PHASE | 6FAE | TS 51.011 10.3.19 | 1 B | phase id (00/02/03) | S1 | **3.34** |
|
||||
| EF.HPPLMN | 6F31 | TS 31.102 4.2.6 | 1 B | search-period multiplier | S1 | **3.34** |
|
||||
| EF.AD | 6FAD | TS 31.102 4.2.18 | 4+X | op-mode byte enum, info, MNC length (may be 0 or absent) | S1 | **3.34** |
|
||||
| EF.CBMI | 6F45 | TS 31.102 4.2.14 | 2n | CB message ids | S6 | **3.34** |
|
||||
| EF.CBMID | 6F48 | TS 31.102 4.2.20 | 2n | CB message ids | S6 | **3.34** |
|
||||
| EF.CBMIR | 6F50 | TS 31.102 4.2.22 | 4n | CB id ranges | S6 | **3.34** |
|
||||
| EF.ACL | 6F57 | TS 31.102 4.2.48 | X | version byte + TLV list (DD APNs) | S8-TR | 3.35 |
|
||||
| EF.LOCI | 6F7E | TS 31.102 4.2.17 | 11 B | TMSI/LAI/status | **S0** | — |
|
||||
| EF.PSLOCI | 6F73 | TS 31.102 4.2.23 | 14 B | P-TMSI/RAI/status | **S0** | — |
|
||||
| EF.EPSLOCI | 6FE3 | TS 31.102 4.2.91 | 18 B | GUTI/TAI/status | **S0** | — |
|
||||
| EF.LOCIGPRS | 6F53 | TS 51.011 10.3.33 | 14 B | GPRS location | **S0** | — |
|
||||
| EF.Kc / KcGPRS | 6F20 / 6F52 | TS 31.102 4.4.3.1/4.4.3.2 | 9 B | ciphering keys | **S0** | — |
|
||||
| EF.Keys / KeysPS | 6F08 / 6F09 | TS 31.102 4.2.3/4.2.4 | 33 B | keys + status | **S0** | — |
|
||||
|
||||
### 2.2 Linear fixed / cyclic files
|
||||
|
||||
| EF | FID | Spec | Record | Content | Shape | Wave |
|
||||
|---|---|---|---|---|---|---|
|
||||
| EF.ADN | 6F3A | TS 31.102 4.4.2.3 | X+14 | alpha + len + TON/NPI + 10 B BCD + CCP + EXT1 | S7 | **3.33** |
|
||||
| EF.FDN | 6F3B | TS 31.102 4.2.24 | X+14 | ADN layout (EXT2) | S7 | **3.33** |
|
||||
| EF.SDN | 6F49 | TS 31.102 4.2.29 | X+14 | ADN layout (EXT3) | S7 | **3.33** |
|
||||
| EF.MSISDN | 6F40 | TS 31.102 4.2.26 | X+14 | ADN layout (EXT5) | S7 | **3.33** |
|
||||
| EF.LND | 6F44 | TS 51.011 10.5.8 | X+14 | ADN layout, **cyclic** | S7 | **3.33** |
|
||||
| EF.BDN | 6F4D | TS 31.102 4.2.44 | **X+15** | ADN layout + **Comparison Method Pointer** | S7 | **3.33** (fix) |
|
||||
| EF.PNN | 6FC5 | TS 31.102 4.2.58 | X ≥ 3 | TLVs: 43 full / 45 short / 80 PLMN info | S8 | **3.33** |
|
||||
| EF.OPL | 6FC6 | TS 31.102 4.2.59 | 8 | LAI (PLMN+LAC) + PNN record id | S7 | 3.35 |
|
||||
| EF.ECC (SIM) | 6FB7 | TS 51.011 10.3.27 | 3n | emergency codes (TR) | S6 | 3.35 |
|
||||
| EF.ECC (USIM) | 6FB7 | TS 31.102 4.2.21 | X+4 | code(3) + alpha(X) + category(1) | S7 | 3.35 |
|
||||
| EF.EXT1–EXT5 | 6F4A–6F55 | TS 31.102 4.4.2.4 / 4.2.30/31/45/37 | 13 | type-dependent: additional data / subaddress | S9 | 3.36 |
|
||||
| EF.SMSP | 6F42 | TS 31.102 4.2.27 | 28+Y | alpha + parameter indicators + TP-DA/SCA/PID/DCS/VP | S10 | **3.35** |
|
||||
| EF.SMS | 6F3C | TS 31.102 4.2.25 | 176 | status byte + TPDU | S10 | **postponed (read-only)** |
|
||||
| EF.ARR | 2F06/6F06 | TS 31.102 4.2.55 | X | access-rule TLVs (ISO 7816-4) | S8/S9 | **postponed (read-only)** |
|
||||
| EF.DIR | 2F00 | TS 51.011 10.3.3 | X | application template TLVs (61 > 4F/50/73…) | S8/S9 | **postponed (read-only)** |
|
||||
| EF.PBR | 4F30/4FXX | TS 31.102 4.4.2.1 | X | BER-TLV: A8/A9/AA containers, C0–CC links | S9 | **postponed (read-only)** |
|
||||
| EF.EPSNSC | 6FE4 | TS 31.102 4.2.92 | — | NAS security context | **S0** | — |
|
||||
| EF.ICI/OCI (call logs) | 6F80/6F81 | TS 31.102 4.2.33/4.2.34 | X+28/X+27 | ADN-like + time/date → cyclic, ME-written | **S0** (plan) | — |
|
||||
|
||||
### 2.3 Coverage vs the spec
|
||||
|
||||
The decoder registry covers ~45 EFs. The spec defines many more; the
|
||||
remaining ones are either specialized protocols (MMS, VGCS/VBS, ePDG, IMS
|
||||
configuration, GBA, 5G slice configuration) or administrative/key files that
|
||||
must not be edited from a workbench without a dedicated flow (Keys, DCK,
|
||||
network parameters, START-HFN). Nothing in the survey forces a new shape
|
||||
beyond S9/S10.
|
||||
|
||||
## 3. Findings (defects and constraints the survey exposed)
|
||||
|
||||
1. **EF.BDN is X+15, not X+14.** The shared ADN codec assumes the last two
|
||||
bytes are CCP1/EXT1; BDN adds a trailing **Comparison Method Pointer**
|
||||
(`X+15`, TS 31.102 4.2.44). The read-only decoded view currently
|
||||
mis-parses BDN records, and the v3.33.0 encoder would corrupt them. The
|
||||
codec must take the layout (ADN vs BDN) and preserve the CMI pointer.
|
||||
2. **EF.ECC has two layouts.** SIM: transparent 3n (TS 51.011 10.3.27);
|
||||
USIM: linear fixed X+4 (TS 31.102 4.2.21). The decoder implements the SIM
|
||||
layout only; on a USIM the alpha bytes are read as more codes. A
|
||||
structure-aware decode is needed before an ECC editor.
|
||||
3. **S8 is not one shape.** PNN (flat tag+length+value TLVs) is the simple
|
||||
case. DIR/ARR/PBR nest TLVs and cross-reference other files; they need
|
||||
either a tree editor or a structured read-only view first.
|
||||
4. **References are part of the model.** EXT1–5 chains (ADN/MSISDN/BDN),
|
||||
OPL→PNN record ids, PBR→(ADN/PBC/EXT/IAP) by FID/SFI. An editor that
|
||||
ignores them can leave a card in a contradictory state: encoders must
|
||||
preserve untouched references and refuse edits that would break one.
|
||||
5. **Cyclic files edit like linear ones** for our purposes (write by record
|
||||
number), but the card owns the order (LND/ICI/OCI/ACM); the editor notes
|
||||
it and the write is still a plain UPDATE.
|
||||
6. **FID collisions across DFs** (e.g. 6F02 = EF.IMPI in ISIM and EF.OCST in
|
||||
USIM) make the fid-only decoder fallback unsafe; the name-based lookup is
|
||||
the primary one and encoders should not be fid-only where a collision is
|
||||
possible.
|
||||
7. **Text coding is per file.** Annex A (TS 102 221) for SPN/ADN/SUUME;
|
||||
the Network Name IE (TS 24.008 10.5.3.5a) for PNN, where the first byte is
|
||||
`0x80 | spare` for packed GSM 7-bit and `0x90` for UCS-2 — the real-card
|
||||
vector `43 0A 82 D4 32 BB BC 7E B7 5D E4 32 …` confirms it.
|
||||
8. **EF.SST is a 2-bit table** (TS 51.011 10.3.7): 4 services per byte, first
|
||||
bit of the pair = allocated, second = activated. The shared 1-bit
|
||||
UST/EST codec was used for it, so SIM service tables were both misread and
|
||||
miswritten; the decoder is fixed in v3.34.0, editing waits for an
|
||||
allocated/activated UI.
|
||||
9. **EF.ACC class numbering** (TS 31.102 4.2.15): byte 1 b8…b1 = classes 15…8
|
||||
and byte 2 = 7…0 (class 10 is written as 0); the old decoder counted from
|
||||
class 0 at the MSB. Fixed in v3.34.0.
|
||||
10. **EF.AD's operation mode is a whole byte** (00/80/01/81/02/04), not bits
|
||||
0-1 plus a test bit; the MNC-length byte may be 0 (service n°130 available)
|
||||
or absent (3-byte SIM form). The decoder and encoder follow this now.
|
||||
11. **The IMSI parity/type nibble** (TS 24.008 10.5.1.4) is 9 for an odd digit
|
||||
count and 1 for an even one; the first encoder always wrote 9.
|
||||
12. **SIM EF.LP/EF.PL use the 1-byte DCS language coding**, unlike the USIM
|
||||
EF.LI's 2-byte ISO 639 letters — the shared decoder is wrong for the SIM
|
||||
files (queued with the language work).
|
||||
13. **EF.SMSP's parameter indicators are a five-bit presence map with inverted
|
||||
meaning** (TS 31.102 §4.2.27: bit 1 DA, 2 SCA, 3 PID, 4 DCS, 5 VP; bit = 1
|
||||
means *absent*; bits 6–8 reserved = 1). The decoder had the bits shifted
|
||||
(SCA on 5, PID on 2, DCS on 3, VP on 4), so mixed indicators were misread.
|
||||
14. **The two address slots use different length conventions**: TP-DA's length
|
||||
is the digit count (TS 23.040 §9.1.2.5), TS-SCA's is the octet count of
|
||||
TON/NPI + digits with 0 = default SMSC (TS 24.011 §8.2.5.2); the decoder
|
||||
read the DA slot as octets. Both are handled explicitly now.
|
||||
|
||||
## 4. Release waves
|
||||
|
||||
- **v3.33.0 (shipped)** — framework: encoder registry, field/list/items
|
||||
schemas, record checkboxes + auto-check, save/validation; shapes S2, S5,
|
||||
S7, S8 for SPN, PLMNwAcT/OPLMNwAcT/HPLMNwAcT, ADN/FDN/SDN/MSISDN/LND,
|
||||
PNN; the BDN layout fix; the `efPlmnAct` EC-GSM-IoT-only (`0x88`) decode fix.
|
||||
- **v3.34.0 (shipped)** — the S1/S3/S4/S6 wave: UST/IST/EST service tables
|
||||
(filterable bit grid), ACC, PHASE, HPPLMN, AD, the PLMN-only lists
|
||||
(PLMNsel/FPLMN/EHPLMN), ICCID/IMSI (behind a confirmation) and
|
||||
CBMI/CBMID/CBMIR. Decoder fixes: ACC class numbering, the AD operation
|
||||
mode / MNC-length rules, the SST 2-bit layout (read-only for now) and the
|
||||
IMSI parity nibble.
|
||||
- **v3.35.0 (shipped)** — EF.SMSP as the first S10 dedicated builder: the full
|
||||
parameter form (alpha; TP-DA and TS-SCA with their own length conventions
|
||||
and TON; TP-PID; TP-DCS with TS 23.038 hints; the relative TP-VP with the
|
||||
TS 23.040 ladder), a per-parameter *absent (the ME default applies)* state,
|
||||
verbatim preservation of unchanged slots (including alphanumeric TON
|
||||
addresses) and the parameter-indicator and TP-DA-length decoder fixes.
|
||||
- **v3.36.0** — LI/LP/PL (the SIM files need the 1-byte DCS language coding
|
||||
and a name-dependent decoder), GID1/GID2 (the clause states no coding —
|
||||
verify TS 31.101 first), SPDI (the A3 > 80 wrapper), OPL (PNN record
|
||||
select), ECC (SIM and USIM — needs a structure-aware decode), SUUME,
|
||||
IMPI/DOMAIN/IMPU, ACL; EXT1–EXT5 as a read-only structured view.
|
||||
- **Postponed by decision (read-only; do not implement without confirmation)**
|
||||
— EF.DIR, EF.ARR, EF.SMS, EF.PBR: they rarely need manual changes and the
|
||||
decoded view is enough.
|
||||
- **Read-only forever (S0)** unless a dedicated flow appears: LOCI/PSLOCI/
|
||||
EPSLOCI/EPSNSC/LOCIGPRS, Kc/KcGPRS, Keys/KeysPS, ACM, START-HFN/THRESHOLD,
|
||||
call logs (ME-written), DCK/NETPAR.
|
||||
|
||||
## 5. Testing strategy
|
||||
|
||||
- **Round-trip**: for every encoder, `encode(decode(original), original)`
|
||||
must reproduce the original bytes for spec-canonical records (the vectors
|
||||
come from the spec's own examples, pySim's test data and real-card dumps).
|
||||
- **Edit-one-field**: changing a field changes only that field's bytes
|
||||
(padding, coding of untouched fields, ids and extension pointers stay).
|
||||
- **Refusals**: over-capacity values, broken references, unsupported layouts
|
||||
(BDN with the CMI pointer, EXT1 chains) throw an actionable error.
|
||||
- **No capability by accident**: a file without an encoder must never show
|
||||
the editor (gating test), and a schema/label drift guard keeps the editors
|
||||
and decoders aligned.
|
||||
@@ -407,8 +407,9 @@
|
||||
<h3 id="file-manager" class="text-lg font-medium mb-2">6.1 Файловый менеджер</h3>
|
||||
<p class="text-sm mb-2">Дерево файловой системы отображается слева; выбор файла открывает панель деталей справа. Элементы сгруппированы: DF выше EF, сортировка по <strong>FID</strong> или символьному <strong>имени</strong> (пиллы и кнопка <strong>«Проверить все файлы»</strong> закреплены над прокручиваемым деревом; выбор сохраняется в <code class="font-mono text-sm">localStorage</code>). При выборе файла над содержимым также показываются FID, тип файла, размер / структура записей и декодированный FCI — в рамке с символьным именем файла. Если FCI ссылается на свои правила доступа через запись <code class="font-mono text-sm">EF.ARR</code> (атрибут безопасности <code class="font-mono text-sm">8B</code>), правила указанной записи показываются с отступом под этой строкой: они берутся из копий <code class="font-mono text-sm">EF.ARR</code> карты — копии MF (<code class="font-mono text-sm">2F06</code>), копий DF.TELECOM/DF.GSM (<code class="font-mono text-sm">6F06</code>) и копии ADF.USIM (<code class="font-mono text-sm">6F06</code>): читаются один раз за сессию при equip, обновляются при сбросе/equip и после записи <code class="font-mono text-sm">EF.ARR</code>; снимок разрешает те же ссылки из собственной захваченной копии. Побеждает копия собственного DF файла, с подъёмом до MF; ссылка, для которой в этой цепочке копии нет, остаётся неразрешённой, если только кандидат не единственный.</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Читать сырые данные / Декодировать</strong> — чтение выбранного файла в виде hex-дампа или таблицы декодированных полей (клиентские декодеры EF: IMSI, ICCID, SPN, списки PLMN, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); серверный pySim JSON того же чтения остаётся доступен в свёрнутом блоке <em>pySim JSON (сервер)</em>. Подсвеченная кнопка — текущий вид; нажатие любой из них (пере)читывает файл. «Декодировать» недоступна, если для файла нет клиентского декодера (проверка по имени/FID до чтения).</li>
|
||||
<li><strong>Редактировать сырые данные</strong> — изменение hex-данных, <strong>Сохранить</strong> для записи (или <strong>Отмена</strong>); transparent-файлы редактируются в textarea, record-файлы — по одному полю на запись с флажками выбора. Декодированный вид доступен только для чтения — «Редактировать сырые данные» сначала переключает на hex-вид и при необходимости читает файл.</li>
|
||||
<li><strong>Читать сырые данные / Декодировать</strong> — чтение выбранного файла в виде hex-дампа или таблицы декодированных полей (клиентские декодеры EF: IMSI, ICCID, SPN, списки PLMN, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, таблицы сервисов, SUME, …); таблица декодируется в браузере из тех же сырых байт (без серверного декодирования). Подсвеченная кнопка — текущий вид; нажатие любой из них (пере)читывает файл. «Декодировать» недоступна, если для файла нет клиентского декодера (проверка по имени/FID до чтения).</li>
|
||||
<li><strong>Редактировать сырые данные</strong> — изменение hex-данных, <strong>Сохранить</strong> для записи (или <strong>Отмена</strong>); transparent-файлы редактируются в textarea, record-файлы — по одному полю на запись с флажками выбора. «Редактировать сырые данные» сначала переключает на hex-вид и при необходимости читает файл.</li>
|
||||
<li><strong>Редактировать декодированные</strong> — редактирование файла в его собственной декодированной форме; кодирование выполняется в браузере, запись идёт обычным путём обновления. Кодировщики покрывают <strong>EF.SPN</strong>, <strong>EF.PNN</strong>, списки PLMN (<strong>PLMNsel / FPLMN / EHPLMN</strong> и списки с технологиями доступа <strong>PLMNwAcT / OPLMNwAcT / HPLMNwAcT</strong>), файлы номеров (<strong>ADN / FDN / SDN / MSISDN / LND / BDN</strong>), таблицы сервисов (<strong>UST / IST / EST</strong>; <strong>SST</strong> остаётся только для чтения до появления редактора allocated/activated), <strong>ACC</strong>, <strong>PHASE</strong>, <strong>HPPLMN</strong>, <strong>AD</strong>, <strong>ICCID / IMSI</strong> (с подтверждением — запись меняет идентичность карты) списки cell broadcast (<strong>CBMI / CBMID / CBMIR</strong>) и <strong>EF.SMSP</strong> (полная форма SMS-параметров: alpha, TP-DA и TS-SCA с их правилами адресации и TON, TP-PID, TP-DCS и относительный TP-VP; у каждого параметра — флажок <em>«отсутствует (действует значение ME по умолчанию)»</em> и живые подсказки по DCS и сроку действия). Для transparent-файла — одна форма (поля SPN, строки PLMN с добавлением/удалением, элементы PNN с добавлением/удалением); для record-файла — по форме на запись с флажком выбора, который ставится автоматически при изменении поля, поэтому записываются только изменённые записи. <strong>Сохранить</strong> проверяет все выбранные записи до записи; для файлов без кодировщика кнопка недоступна с подсказкой. Указатели расширений (EF.EXT1–EF.EXT5) и байт метода сравнения BDN сохраняются; редактирование номера, продолжение которого лежит в файле расширения, отклоняется.</li>
|
||||
<li><strong>Проверить все файлы</strong> — обход всего дерева (включая пользовательские файлы) с пометкой каждого элемента: есть (обычный вид) или нет (красный ✗, без стрелки разворачивания); существующие пустые DF показывают <code class="font-mono text-sm">(пусто)</code>. Отображается прогресс <em>N / всего</em>, обход можно остановить; в конце — сводка «есть/нет». Файлы проверяются только при разворачивании или проверке — просмотр остаётся ленивым.</li>
|
||||
<li><strong>ADM</strong> — файлы, требующие администраторский PIN, возвращают <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; если в подходящем пресете карты (тот же ICCID) есть ключ ADM, рядом с ошибкой появляется кнопка <strong>«Проверить ADM»</strong>, а значок в заголовке (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) становится кликабельным. Каждый неверный ключ расходует попытку: остаток показывается, а повторная попытка требует подтверждения. Заблокированный ADM здесь не восстановить — нужен ключ разблокировки карты.</li>
|
||||
</ul>
|
||||
|
||||
+3
-2
@@ -406,8 +406,9 @@
|
||||
<h3 id="file-manager" class="text-lg font-medium mb-2">6.1 File manager</h3>
|
||||
<p class="text-sm mb-2">The file system tree is displayed on the left; selecting a file opens its detail pane on the right. Entries are grouped with DFs above EFs and sorted by <strong>FID</strong> or symbolic <strong>Name</strong> (pills pinned above the scrolling tree together with <strong>Probe all files</strong>; the choice is remembered in <code class="font-mono text-sm">localStorage</code>). Selecting a file also shows its FID, file type, size / record layout and the decoded FCI — in a bordered block labelled with the file’s symbolic name — above the content pane. When the FCI references its access rules through an <code class="font-mono text-sm">EF.ARR</code> record (security attribute <code class="font-mono text-sm">8B</code>), the rules of the referenced record are shown as indented lines under that row: they are resolved from the card’s <code class="font-mono text-sm">EF.ARR</code> copies — the MF copy (<code class="font-mono text-sm">2F06</code>), the DF.TELECOM/DF.GSM copies (<code class="font-mono text-sm">6F06</code>) and the ADF.USIM copy (<code class="font-mono text-sm">6F06</code>): read once per card session at equip, refreshed on reset/equip and after an <code class="font-mono text-sm">EF.ARR</code> write; a snapshot resolves the same references from its own captured copy. The copy of the file’s own DF wins, walking up to MF; a reference with no copy in that chain stays unresolved unless a single candidate exists.</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Read raw / Read decoded</strong> — read the selected file as a hex dump or as a decoded field table (client-side EF decoders: IMSI, ICCID, SPN, PLMN lists, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the server-side pySim JSON of the same read stays available in the collapsed <em>pySim JSON (server)</em> disclosure. The highlighted pill is the current view; clicking either pill (re-)reads the file. <strong>Read decoded</strong> is disabled when the client-side decoders do not cover the file (name/FID check, before reading).</li>
|
||||
<li><strong>Edit raw</strong> — edit the raw hex data, <strong>Save</strong> to write back (or <strong>Cancel</strong>); transparent files use a textarea, record files one input per record with selection checkboxes. The decoded view is read-only — <strong>Edit raw</strong> switches to the hex view first and reads the file if needed.</li>
|
||||
<li><strong>Read raw / Read decoded</strong> — read the selected file as a hex dump or as a decoded field table (client-side EF decoders: IMSI, ICCID, SPN, PLMN lists, LOCI/PSLOCI/EPSLOCI, ADN/MSISDN, service tables, SUME, …); the decoded table is built in the browser from the same raw bytes (no server-side decode). The highlighted pill is the current view; clicking either pill (re-)reads the file. <strong>Read decoded</strong> is disabled when the client-side decoders do not cover the file (name/FID check, before reading).</li>
|
||||
<li><strong>Edit raw</strong> — edit the raw hex data, <strong>Save</strong> to write back (or <strong>Cancel</strong>); transparent files use a textarea, record files one input per record with selection checkboxes. <strong>Edit raw</strong> switches to the hex view first and reads the file if needed.</li>
|
||||
<li><strong>Edit decoded</strong> — edit the file through its own decoded form, encoded in the browser and written with the normal update path. The encoders cover <strong>EF.SPN</strong>, <strong>EF.PNN</strong>, the PLMN lists (<strong>PLMNsel / FPLMN / EHPLMN</strong> and the access-technology lists <strong>PLMNwAcT / OPLMNwAcT / HPLMNwAcT</strong>), the dialling-number files (<strong>ADN / FDN / SDN / MSISDN / LND / BDN</strong>), the service tables (<strong>UST / IST / EST</strong>; <strong>SST</strong> stays read-only until the SIM allocated/activated editor exists), <strong>ACC</strong>, <strong>PHASE</strong>, <strong>HPPLMN</strong>, <strong>AD</strong>, <strong>ICCID / IMSI</strong> (behind a confirmation — writing them changes the card identity) the cell-broadcast lists (<strong>CBMI / CBMID / CBMIR</strong>) and <strong>EF.SMSP</strong> (the full SMS-parameter form: alpha, TP-DA and TS-SCA with their addressing rules and TON, TP-PID, TP-DCS and the relative TP-VP, each with an <em>absent (the ME default applies)</em> toggle and live DCS/validity hints). A transparent file gets one form (SPN fields, PLMN rows with add/remove, PNN items with add/remove); a record file gets one form per record with a selection checkbox that is auto-checked when a field changes, so only the edited records are written. <strong>Save</strong> validates every selected record before writing anything; files without an encoder keep the button disabled with a tooltip. Extension pointers (EF.EXT1–EF.EXT5) and the BDN comparison-method byte are preserved, and editing a number that continues in an extension file is refused.</li>
|
||||
<li><strong>Probe all files</strong> — walks the whole tree (including custom files) and marks every entry present (normal) or absent (red ✗, no expand arrow); empty-but-present DFs show <code class="font-mono text-sm">(empty)</code>. Shows progress <em>N / total</em>, can be stopped, and finishes with a present/absent summary. Files are only verified when expanded or probed — browsing stays lazy.</li>
|
||||
<li><strong>ADM</strong> — files that need the administrator PIN fail with <code class="font-mono text-sm">6982</code>/<code class="font-mono text-sm">9804</code>; if the matching card preset (same ICCID) carries an ADM key, a <strong>Verify ADM</strong> button appears next to the error and the header badge (<code class="font-mono text-sm">ADM ✓/✗ ⚿</code>) becomes clickable. Every wrong key consumes an attempt: the remaining attempts are shown and a retry asks for confirmation. A blocked ADM cannot be recovered here — it needs the card’s unblock key.</li>
|
||||
</ul>
|
||||
|
||||
+1917
-70
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v463';
|
||||
const CACHE = 'simple-v469';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -57,9 +57,10 @@ test('pysimAdmSecuritySw flags only the access-condition SWs', () => {
|
||||
});
|
||||
|
||||
test('file-manager security failures route through the ADM hint', () => {
|
||||
// no raw SW error rendering is left (all three sites use the hint helper)
|
||||
// no raw SW error rendering is left (every read/write site, the raw and
|
||||
// the decoded edit paths, uses the hint helper)
|
||||
assert.ok(!/statusEl\.textContent = 'SW: ' \+ \(data\.sw/.test(html), 'raw SW error rendering is gone');
|
||||
assert.strictEqual((html.match(/pysimFsShowError\(statusEl, data\.sw/g) || []).length, 3);
|
||||
assert.strictEqual((html.match(/pysimFsShowError\(statusEl, data\.sw/g) || []).length, 6);
|
||||
assert.ok(html.includes("btn.textContent = t('Verify ADM')"));
|
||||
assert.ok(html.includes("el.setAttribute('onclick', 'pysimVerifyAdm()')"));
|
||||
});
|
||||
|
||||
@@ -24,10 +24,10 @@ function extractFunc(src, name) {
|
||||
const FNS = [
|
||||
'efBytes', 'efHex', 'efSwapNibbles', 'efAllFf', 'efRstripFf', 'efTlv', 'efUcs2Be',
|
||||
'efGsm7Octets', 'efAnnexA', 'efBcdAddress', 'efBcdAddressLen', 'efPlmn', 'efPlmnAct', 'efPnnText',
|
||||
'efDecIccid', 'efDecImsi', 'efDecLi', 'efDecServiceTable', 'efDecPlmnList', 'efDecFplmn', 'efDecPlmnWact',
|
||||
'efDecIccid', 'efDecImsi', 'efDecLi', 'efDecServiceTable', 'efDecSst', 'efDecPlmnList', 'efDecFplmn', 'efDecPlmnWact',
|
||||
'efDecSpn', 'efDecLoci', 'efDecPsLoci', 'efDecEpsLoci', 'efDecEpsNsc', 'efDecKc', 'efDecAcc',
|
||||
'efDecPhase', 'efDecCbmi', 'efDecCbmir', 'efDecEcc', 'efDecOpl', 'efDecAd', 'efDecAcl',
|
||||
'efDecSmsp', 'efDecSpdi', 'efDecNai', 'efDecDir', 'efDecArr', 'efDecPnn', 'efDecAdn',
|
||||
'efDecPhase', 'efDecCbmi', 'efDecCbmir', 'efDecHpplmn', 'efDecEcc', 'efDecOpl', 'efDecAd', 'efDecAcl',
|
||||
'efSmspDaNumber', 'efDecSmsp', 'efDecSpdi', 'efDecNai', 'efDecDir', 'efDecArr', 'efDecPnn', 'efDecAdn',
|
||||
'pinKeyRefName', 'efArrAmLabel', 'efArrSc', 'efArrAmDo',
|
||||
'efDecExt1', 'efDecSms', 'efDecSume', 'efPbrHex', 'efDecPbr', 'efFindDecoder', 'efFidFromPath', 'efDecodeBytes',
|
||||
'efDecodeFile', 'efDecodeHex', 'efIsRawOnly', 'efFieldLabel', 'efPrimitive', 'efFlatten',
|
||||
@@ -42,7 +42,7 @@ for (const f of FNS) code += extractFunc(html, f) + '\n';
|
||||
for (const c of ['GSM7_ALPHABET', 'GSM7_EXT_MAP', 'FCP_ARR_AM_BITS']) {
|
||||
code += html.match(new RegExp('const ' + c + ' = [\\s\\S]*?\\n\\];'))[0].replace('const ', 'var ') + '\n';
|
||||
}
|
||||
for (const c of ['EF_UST_SERVICES', 'EF_SST_SERVICES', 'EF_IST_SERVICES', 'EF_PHASE_NAMES', 'EF_PBR_KINDS']) {
|
||||
for (const c of ['EF_UST_SERVICES', 'EF_SST_SERVICES', 'EF_IST_SERVICES', 'EF_EST_SERVICES', 'EF_PHASE_NAMES', 'EF_PBR_KINDS']) {
|
||||
code += html.match(new RegExp('const ' + c + ' = \\{.*\\};'))[0].replace('const ', 'var ') + '\n';
|
||||
}
|
||||
code += html.match(/const EF_FIELD_LABELS = \{[\s\S]*?\n\};/)[0].replace('const ', 'var ') + '\n';
|
||||
@@ -103,7 +103,9 @@ test('EF decoders match the pinned spec / pySim test vectors', () => {
|
||||
assert.deepStrictEqual(efDecLi(efBytes('FFFF')), { languages: [] });
|
||||
assert.deepStrictEqual(efDecSpn(efBytes('0147534d2d52204348ffffffffffffffff')), { show_in_hplmn: true, hide_in_oplmn: false, name: 'GSM-R CH' });
|
||||
assert.deepStrictEqual(efDecAcc(efBytes('0000')), { access_classes: [] });
|
||||
assert.deepStrictEqual(efDecAcc(efBytes('8000')), { access_classes: [0] });
|
||||
assert.deepStrictEqual(efDecAcc(efBytes('8000')), { access_classes: [15] });
|
||||
assert.deepStrictEqual(efDecAcc(efBytes('0001')), { access_classes: [0] });
|
||||
assert.deepStrictEqual(efDecAcc(efBytes('8001')), { access_classes: [15, 0] });
|
||||
assert.deepStrictEqual(efDecEcc(efBytes('19f101')), { number: '911', esc: '0x01' });
|
||||
assert.deepStrictEqual(efDecKc(efBytes('837d783609a3858f05')), { kc: '837D783609A3858F', ksi: 0 });
|
||||
assert.deepStrictEqual(efDecPlmnList(efBytes('22F860')), { plmns: [{ mcc: '228', mnc: '06' }] });
|
||||
@@ -115,7 +117,7 @@ test('EF decoders match the pinned spec / pySim test vectors', () => {
|
||||
assert.strictEqual(efFindDecoder('EF.FPLMN', null).fn, efDecFplmn);
|
||||
assert.deepStrictEqual(efDecCbmi(efBytes('0010FFFF0020')), { message_ids: [16, 32] });
|
||||
assert.deepStrictEqual(efDecCbmir(efBytes('0000FFFEFFFF')), { ranges: [['0000', 'FFFE']] });
|
||||
assert.deepStrictEqual(efDecPhase(efBytes('03')), { phase: 'phase 2 and higher' });
|
||||
assert.deepStrictEqual(efDecPhase(efBytes('03')), { phase: 'phase 2 and PROFILE DOWNLOAD required' });
|
||||
assert.deepStrictEqual(efDecNai(efBytes('8021696d732e6d6e633030302e6d63633733382e336770706e6574776f726b2e6f7267')),
|
||||
{ text: 'ims.mnc000.mcc738.3gppnetwork.org' });
|
||||
assert.deepStrictEqual(efDecImsi(efBytes('FFFF')), { empty: true });
|
||||
@@ -276,6 +278,9 @@ test('EF record decoders: SMS, OPL, DIR, ARR, IMPI/SMSP', () => {
|
||||
assert.strictEqual(smsp.sca, '+4915790109999');
|
||||
assert.strictEqual(smsp.tp_pid, '0x00');
|
||||
assert.strictEqual(smsp.tp_dcs, '0x00');
|
||||
assert.strictEqual(smsp.tp_vp, '0xA9');
|
||||
assert.strictEqual(smsp.indicators, '0xE1');
|
||||
assert.strictEqual(smsp.tp_da, undefined);
|
||||
// EF.SMS status byte + skip SMSC + TPDU
|
||||
const sms = efDecSms(efBytes('030891945197109099f90000'));
|
||||
assert.strictEqual(sms.direction, 'MT');
|
||||
@@ -283,6 +288,31 @@ test('EF record decoders: SMS, OPL, DIR, ARR, IMPI/SMSP', () => {
|
||||
assert.strictEqual(sms.tpdu, '0000');
|
||||
});
|
||||
|
||||
test('EF.SMSP decodes the indicator bits (TS 31.102 4.2.27)', () => {
|
||||
// live card record: DA absent (0x01), SCA present, PID present, DCS absent
|
||||
// (0x08), VP present; bits 6..8 reserved = 1 -> 0xE9
|
||||
const live = efDecSmsp(efBytes('ffffffffffffffffffffffffffe9ffffffffffffffffffffffff07919733824009f0ffffffff0000ff'));
|
||||
assert.deepStrictEqual(live, {
|
||||
indicators: '0xE9',
|
||||
sca: '+79332804900',
|
||||
tp_pid: '0x00',
|
||||
tp_vp: '0xFF',
|
||||
});
|
||||
assert.strictEqual(live.tp_da, undefined);
|
||||
assert.strictEqual(live.tp_dcs, undefined); // DCS absent, its 00 slot kept raw for the editor
|
||||
});
|
||||
|
||||
test('EF.SMSP reads the TP-DA length as a digit count (00 padding is not ABS)', () => {
|
||||
// alpha FF x13 | indicator FE (DA present, rest absent) |
|
||||
// DA 04 81 21 43 00 00 FF... ("1234" padded with 00, not FF)
|
||||
const hex = 'FF'.repeat(13) + 'FE' + '048121430000FFFFFFFFFFFF'
|
||||
+ 'FF'.repeat(12) + 'FF' + 'FF' + 'FF';
|
||||
assert.deepStrictEqual(efDecSmsp(efBytes(hex)), {
|
||||
indicators: '0xFE',
|
||||
tp_da: '1234',
|
||||
});
|
||||
});
|
||||
|
||||
test('service tables: UST service bits and IST fallback for 6F07', () => {
|
||||
const ust = efDecServiceTable(efBytes('00000010'), EF_UST_SERVICES);
|
||||
assert.deepStrictEqual(ust.services, [{ n: 29, name: 'Data download via SMS-CB' }]);
|
||||
@@ -290,6 +320,29 @@ test('service tables: UST service bits and IST fallback for 6F07', () => {
|
||||
assert.deepStrictEqual(ist.services, [{ n: 1, name: 'P-CSCF address' }]);
|
||||
});
|
||||
|
||||
test('EF.SST is the SIM 2-bit service table (allocated/activated)', () => {
|
||||
// TS 51.011 10.3.7: service 2 ADN -> byte 0, pair start bit 2 (0x04
|
||||
// allocated, 0x08 activated)
|
||||
assert.deepStrictEqual(efDecSst(efBytes('0C')), { services: [
|
||||
{ n: 2, name: 'Abbreviated Dialling Numbers (ADN)', allocated: true, activated: true },
|
||||
] });
|
||||
assert.deepStrictEqual(efDecSst(efBytes('04')), { services: [
|
||||
{ n: 2, name: 'Abbreviated Dialling Numbers (ADN)', allocated: true, activated: false },
|
||||
] });
|
||||
assert.deepStrictEqual(efDecSst(efBytes('08')), { services: [
|
||||
{ n: 2, name: 'Abbreviated Dialling Numbers (ADN)', allocated: false, activated: true },
|
||||
] });
|
||||
// byte 1 pair 3 (bits 6/7) = service 4 SMS; byte 0 pair 1 = service 2
|
||||
assert.deepStrictEqual(efDecSst(efBytes('C0')), { services: [
|
||||
{ n: 4, name: 'Short Message Storage (SMS)', allocated: true, activated: true },
|
||||
] });
|
||||
assert.deepStrictEqual(efDecSst(efBytes('00')), { services: [] });
|
||||
assert.deepStrictEqual(efFindDecoder('EF.SST', null).fn(efBytes('0C')).services[0].activated, true);
|
||||
// the read-only view shows both bits
|
||||
assert.ok(efFlatten(efDecSst(efBytes('0C'))).some(r =>
|
||||
r[0] === 'Service 2' && r[1].includes('allocated') && r[1].includes('activated')));
|
||||
});
|
||||
|
||||
test('decoder registry resolves by name first, then FID; unknown returns null', () => {
|
||||
assert.strictEqual(efFindDecoder('EF.IMSI', null).fid, '6f07');
|
||||
assert.strictEqual(efFindDecoder(null, '2fe2').name, 'EF.ICCID');
|
||||
@@ -325,7 +378,7 @@ test('efFlatten renders fields, services and PLMNs for display', () => {
|
||||
[['Service 5', 'Call Control by USIM']]);
|
||||
assert.deepStrictEqual(efFlatten({ plmns: [{ mcc: '262', mnc: '01', access_tech: 'GSM' }] }),
|
||||
[['PLMN 1', '262-01 (GSM)']]);
|
||||
assert.deepStrictEqual(efFlatten({ show_in_hplmn: true }), [['Show in HPLMN', 'yes']]);
|
||||
assert.deepStrictEqual(efFlatten({ show_in_hplmn: true }), [['Display the network name in HPLMN / an SPDI PLMN', 'yes']]);
|
||||
assert.deepStrictEqual(efFlatten({ title: 'Menu!', icon: '0123' }), [['Title', 'Menu!'], ['Icon', '0123']]);
|
||||
});
|
||||
|
||||
@@ -376,7 +429,10 @@ test('file manager decoded view renders fields, not JSON', () => {
|
||||
const out = pysimFsDecodedHtml({ name: 'EF.IMSI', fid: null }, { success: true, data: '082982608200002080' });
|
||||
assert.match(out, /IMSI/);
|
||||
assert.match(out, /228062800000208/);
|
||||
assert.match(out, /pySim JSON \(server\)/);
|
||||
// The view is the client-side decoder's table only (v3.32.7): the
|
||||
// server-side pySim JSON disclosure is gone - it was an unrelated second
|
||||
// read of the card, not an input to this decode.
|
||||
assert.doesNotMatch(out, /pySim JSON/);
|
||||
assert.doesNotMatch(out, /\{"imsi"/);
|
||||
// the label column is content-sized, not a fixed 10rem block
|
||||
assert.match(out, /grid-template-columns:max-content 1fr/);
|
||||
@@ -387,6 +443,15 @@ test('file manager decoded view renders fields, not JSON', () => {
|
||||
assert.match(raw, /0011/);
|
||||
});
|
||||
|
||||
test('the file manager never requests the server-side decode', () => {
|
||||
// The decoded table is client-side (the raw read's bytes); the pySim JSON
|
||||
// lookup - a second /api/read with mode 'decoded' - is gone (v3.32.7).
|
||||
assert.ok(!html.includes("mode = 'decoded'"));
|
||||
assert.ok(!html.includes('pysimFsLoadPySimJson'));
|
||||
assert.ok(!html.includes('pysim-fs-pysim-json'));
|
||||
assert.ok(!html.includes('pySim JSON (server)'));
|
||||
});
|
||||
|
||||
test('efRenderFieldsHtml sizes the label column to its content', () => {
|
||||
const out = efRenderFieldsHtml([['ICCID', '8988'], ['Very long label', 'x']]);
|
||||
assert.match(out, /style="display:grid;grid-template-columns:max-content 1fr/);
|
||||
@@ -426,3 +491,28 @@ test('profilerRenderReport shows raw and decoded comparisons for content mismatc
|
||||
assert.match(same, /Decoded values match/);
|
||||
assert.doesNotMatch(same, /Decoded comparison/);
|
||||
});
|
||||
|
||||
test('EF.BDN uses the 15-byte ADN layout (TS 31.102 4.2.44)', () => {
|
||||
// alpha 'TEST' | len 06 | TON/NPI 91 | BCD 21 43 65 87 09 + FF padding |
|
||||
// CCP FF | EXT4 FF | EF.CMI comparison-method pointer 03
|
||||
const hex = '54455354' + '06' + '91' + '2143658709' + 'FF'.repeat(7) + '03';
|
||||
const bdn = efBytes(hex);
|
||||
const want = { name: 'TEST', number: '+1234567890', cmi: 3 };
|
||||
assert.strictEqual(bdn.length, 19);
|
||||
assert.deepStrictEqual(efDecAdn(bdn, 15), want);
|
||||
assert.deepStrictEqual(efFindDecoder('EF.BDN').fn(bdn), want);
|
||||
// the 14-byte default and the EF.ADN registry entry are unchanged
|
||||
const adn = efBytes('42204841203120536963FFFFFFFFFFFF06810628560810FFFFFFFFFFFFFF');
|
||||
const adnWant = { name: 'B HA 1 Sic', number: '6082658001' };
|
||||
assert.deepStrictEqual(efDecAdn(adn), adnWant);
|
||||
assert.deepStrictEqual(efFindDecoder('EF.ADN').fn(adn), adnWant);
|
||||
assert.notDeepStrictEqual(efDecAdn(adn, 15), adnWant);
|
||||
});
|
||||
|
||||
test('the BDN extension and CMI fields carry readable labels', () => {
|
||||
assert.deepStrictEqual(efFlatten({ cmi: 3 }), [['Comparison method pointer', '3']]);
|
||||
assert.deepStrictEqual(efFlatten({ ext1: 2 }), [['Extension record', '2']]);
|
||||
const bdn = efDecAdn(efBytes('54455354' + '06' + '91' + '2143658709' +
|
||||
'FF'.repeat(7) + '03'), 15);
|
||||
assert.ok(efFlatten(bdn).some(r => r[0] === 'Comparison method pointer' && r[1] === '3'));
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,8 @@ function extractFunc(src, name) {
|
||||
let code = 'var pysimFsDecodedMode = false;\n'
|
||||
+ 'var pysimFsEditMode = false;\n'
|
||||
+ 'var pysimFsEditData = null;\n'
|
||||
+ 'var pysimFsDecEditMode = false;\n'
|
||||
+ 'var pysimFsDecModel = null;\n'
|
||||
+ 'var pysimFsSelected = null;\n'
|
||||
+ 'var pysimFsTreeRoot = null;\n';
|
||||
for (const fn of ['pysimFsSetMode', 'pysimFsPillsEnabled', 'pysimFsEdit',
|
||||
@@ -115,12 +117,19 @@ test('Read raw / Read decoded pills switch the view and read the file', () => {
|
||||
assert.strictEqual(h.reads(), 0, 'skipRead updates the pills only');
|
||||
assert.ok(h.pill('raw').classList.contains('bg-blue-600'));
|
||||
assert.ok(!h.pill('dec').classList.contains('bg-blue-600'));
|
||||
// an enabled inactive pill is outlined, not grey: the grey fill reads as
|
||||
// "disabled" although the pill is clickable (reported live)
|
||||
assert.ok(h.pill('dec').classList.contains('bg-white'));
|
||||
assert.ok(h.pill('dec').classList.contains('text-blue-600'));
|
||||
assert.ok(!h.pill('dec').classList.contains('bg-gray-200'));
|
||||
|
||||
pysimFsSetMode('dec');
|
||||
assert.strictEqual(pysimFsDecodedMode, true);
|
||||
assert.strictEqual(h.reads(), 1, 'a pill click reads the selected file');
|
||||
assert.ok(h.pill('dec').classList.contains('bg-blue-600'));
|
||||
assert.ok(!h.pill('raw').classList.contains('bg-blue-600'));
|
||||
assert.ok(h.pill('raw').classList.contains('bg-white'));
|
||||
assert.ok(!h.pill('raw').classList.contains('bg-gray-200'));
|
||||
});
|
||||
|
||||
test('pill clicks are ignored while editing', () => {
|
||||
@@ -219,4 +228,7 @@ test('the file manager row exposes the read pills and Edit raw', () => {
|
||||
assert.ok(html.includes('id="pysim-fs-read-raw-btn"') && html.includes('data-needs="card"'));
|
||||
const dec = /<button[^>]*id="pysim-fs-read-dec-btn"[^>]*>/.exec(html);
|
||||
assert.ok(dec && dec[0].includes('data-needs-decoder'), 'the decoded pill is decoder-gated');
|
||||
// the enabled-inactive look is an outline; the grey fill is for disabled
|
||||
assert.ok(dec[0].includes('bg-white') && dec[0].includes('text-blue-600'));
|
||||
assert.ok(!dec[0].includes('bg-gray-200'));
|
||||
});
|
||||
|
||||
@@ -553,6 +553,59 @@ test('ramOpChanged clears the executed status only on a real op change', () => {
|
||||
assert.ok(!els['ram-install-params'].classList.contains('hidden'));
|
||||
});
|
||||
|
||||
test('the RAM preset banner is content-driven (no empty amber flash)', () => {
|
||||
// A view / op switch used to un-hide #ram-preset-warning by the op kind
|
||||
// (v3.28.0): with no warning text the empty bordered box stayed visible
|
||||
// until the next status poll rendered it away - the amber block flashing
|
||||
// on every SCP80/RAM view change. Its visibility belongs to the renderer.
|
||||
const els = fakeRamDocument(['ram-op', 'ram-card-sel', 'ram-preset-warning',
|
||||
'ram-install-params', 'ram-cap-rows', 'ram-compat-rows', 'ram-app-rows',
|
||||
'ram-delete-rows', 'ram-del-package-row', 'ram-preset-cell', 'ram-exec-btn',
|
||||
'ram-batch-panel', 'ram-result', 'ram-explorer', 'ram-steps', 'ram-progress',
|
||||
'sp-preset-warning', 'tar-preset-warning']);
|
||||
els['ram-preset-warning'].classList.add('hidden');
|
||||
const prev = {
|
||||
cards: globalThis.cards,
|
||||
t: globalThis.t,
|
||||
iccid: globalThis._pysimCardIccid,
|
||||
batchFetch: globalThis.ramBatchFetch,
|
||||
};
|
||||
globalThis.cards = [];
|
||||
globalThis._pysimCardIccid = null; // no card session: no warning text
|
||||
globalThis.t = s => s;
|
||||
globalThis.ramBatchFetch = () => {};
|
||||
// The real renderer, defined where the extracted ramOpChanged finds it
|
||||
// (a plain function body sees only the globals).
|
||||
const src = ['cardsNormIccid', 'spPresetWarning', 'ramIsBatchOp', 'ramPresetIdx',
|
||||
'spPresetIdx', 'tarPresetIdx', 'spPresetWarningRender']
|
||||
.map(f => extractFunc(html, f)).join('\n');
|
||||
const real = new Function(src + '\nreturn spPresetWarningRender;')();
|
||||
let calls = 0;
|
||||
globalThis.spPresetWarningRender = () => { calls++; return real(); };
|
||||
try {
|
||||
_ramOpLast = 'explore';
|
||||
els['ram-op'].value = 'explore';
|
||||
ramOpChanged();
|
||||
assert.strictEqual(calls, 1, 'the content-driven renderer must own the banner');
|
||||
assert.ok(els['ram-preset-warning'].classList.contains('hidden'),
|
||||
'an empty banner must not be un-hidden by the op dispatch');
|
||||
// a batch op keeps it hidden as well (the preset resolves per card)
|
||||
els['ram-op'].value = 'batch';
|
||||
ramOpChanged();
|
||||
assert.ok(els['ram-preset-warning'].classList.contains('hidden'));
|
||||
// the visibility is never toggled here by the op kind again
|
||||
assert.ok(!extractFunc(html, 'ramOpChanged').includes("'ram-preset-warning'"));
|
||||
} finally {
|
||||
globalThis.spPresetWarningRender = () => {};
|
||||
if (prev.cards === undefined) delete globalThis.cards; else globalThis.cards = prev.cards;
|
||||
if (prev.t === undefined) delete globalThis.t; else globalThis.t = prev.t;
|
||||
if (prev.iccid === undefined) delete globalThis._pysimCardIccid;
|
||||
else globalThis._pysimCardIccid = prev.iccid;
|
||||
if (prev.batchFetch === undefined) delete globalThis.ramBatchFetch;
|
||||
else globalThis.ramBatchFetch = prev.batchFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('ramRender keeps the selected card preset across rebuilds', () => {
|
||||
const els = fakeRamDocument(['ram-card-sel', 'ram-op', 'ram-install-params', 'ram-result', 'ram-explorer', 'ram-steps', 'ram-progress']);
|
||||
globalThis.cards = [{ name: 'A' }, { name: 'B' }, { name: 'C' }];
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pysim-simple-server"
|
||||
version = "3.32.5"
|
||||
version = "3.35.0"
|
||||
description = "HTTP REST server wrapping pysim for the SIMple PWA"
|
||||
license = { text = "GPL-2.0-or-later" }
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE
|
||||
from osmocom.utils import rpad
|
||||
|
||||
|
||||
VERSION = '3.32.5'
|
||||
VERSION = '3.35.0'
|
||||
|
||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||
|
||||
@@ -249,6 +249,24 @@ def _strip_ansi(text):
|
||||
return re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', text)
|
||||
|
||||
|
||||
def _cmd_exception(output):
|
||||
"""The first non-empty line of a cmd2/pySim exception captured from a
|
||||
command's output, or None.
|
||||
|
||||
cmd2 catches a command's exception, prints ``EXCEPTION of type ...`` and
|
||||
(in debug mode) a traceback instead of re-raising, so the HTTP handlers
|
||||
that only look for ``got (XXXX)`` reported success while nothing was
|
||||
written. Both markers are command-level failure evidence.
|
||||
"""
|
||||
text = output or ''
|
||||
if 'EXCEPTION of type' not in text and 'Traceback (most recent call last)' not in text:
|
||||
return None
|
||||
for line in text.splitlines():
|
||||
if line.strip():
|
||||
return line.strip()
|
||||
return 'command failed'
|
||||
|
||||
|
||||
def _parse_help_text(text):
|
||||
result = {'usage': '', 'description': '', 'args': []}
|
||||
lines = text.split('\n')
|
||||
@@ -9843,6 +9861,12 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
sys.stderr = old_stderr
|
||||
finally:
|
||||
apdu_times = _end_apdu_time_collection()
|
||||
exc = _cmd_exception(output)
|
||||
if exc:
|
||||
resp = {'success': False, 'error': exc}
|
||||
self._send_json(resp)
|
||||
self._log_resp(resp)
|
||||
return
|
||||
sw_match = re.search(r'SW:\s*(\w+)', output)
|
||||
err_match = re.search(r'got (\w+)', output)
|
||||
if err_match:
|
||||
@@ -9931,6 +9955,12 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
finally:
|
||||
app.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
exc = _cmd_exception(output)
|
||||
if exc:
|
||||
resp = {'success': False, 'error': exc}
|
||||
self._send_json(resp)
|
||||
self._log_resp(resp)
|
||||
return
|
||||
sw_match = re.search(r'SW:\s*(\w+)', output)
|
||||
err_match = re.search(r'got (\w+)', output)
|
||||
if err_match:
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""cmd2/pySim exception output must fail /api/write and /api/read.
|
||||
|
||||
`onecmd_plus_hooks()` catches a command's exception and prints
|
||||
``EXCEPTION of type ...`` (plus a traceback in debug mode) instead of
|
||||
re-raising, so the handlers that only looked for ``got (XXXX)`` reported a
|
||||
successful write while nothing was written. The handlers now treat both
|
||||
markers as failure; the SW-based success/failure semantics stay as before.
|
||||
"""
|
||||
|
||||
import types
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from pysim_simple_server import server as srv
|
||||
from pysim_simple_server.server import PysimHandler, _cmd_exception
|
||||
|
||||
|
||||
class FakeApp:
|
||||
def __init__(self, output):
|
||||
self.output = output
|
||||
self.stdout = None
|
||||
self.rs = types.SimpleNamespace(lchan=[types.SimpleNamespace(selected_file=None)])
|
||||
|
||||
def onecmd_plus_hooks(self, cmd):
|
||||
# pySim writes into the app's stdout, which the handler redirects
|
||||
self.stdout.write(self.output)
|
||||
|
||||
|
||||
def _fake_handler(path, body, app):
|
||||
h = object.__new__(PysimHandler)
|
||||
h.path = path
|
||||
h.command = 'POST'
|
||||
h.headers = {}
|
||||
h.server = types.SimpleNamespace(app=app, log_requests=False, pbr=None)
|
||||
h._read_body = lambda: body
|
||||
h._log_req = lambda *a, **k: None
|
||||
h._log_resp = lambda *a, **k: None
|
||||
sent = []
|
||||
h._send_json = lambda data, status=200: sent.append((data, status))
|
||||
return h, sent
|
||||
|
||||
|
||||
class CmdExceptionTests(unittest.TestCase):
|
||||
def test_the_helper_detects_both_markers_and_returns_the_first_line(self):
|
||||
self.assertIsNone(_cmd_exception(''))
|
||||
self.assertIsNone(_cmd_exception('SW: 9000\n'))
|
||||
exc = ("EXCEPTION of type 'ValueError' occurred with message: 'boom'\n"
|
||||
"Traceback (most recent call last):\n File x\nValueError: boom\n")
|
||||
self.assertEqual(_cmd_exception(exc),
|
||||
"EXCEPTION of type 'ValueError' occurred with message: 'boom'")
|
||||
tb = 'Traceback (most recent call last):\n File x\nRuntimeError: boom\n'
|
||||
self.assertEqual(_cmd_exception(tb), 'Traceback (most recent call last):')
|
||||
|
||||
|
||||
class WriteExceptionTests(unittest.TestCase):
|
||||
def _write(self, output):
|
||||
app = FakeApp(output)
|
||||
h, sent = _fake_handler('/api/write', {
|
||||
'name': 'EF.ADN', 'data': 'AA' * 30, 'record_nr': 1, 'allow_probe': True,
|
||||
}, app)
|
||||
body = {'name': 'EF.ADN', 'data': 'AA' * 30, 'record_nr': 1, 'allow_probe': True}
|
||||
h._read_body = lambda: body
|
||||
with mock.patch.object(srv, '_select_with_parent', lambda *a, **k: (None, None)), \
|
||||
mock.patch.object(srv, '_get_file_type', lambda *a: 'linear_fixed'), \
|
||||
mock.patch.object(srv, '_arr_refresh_after_write', lambda *a, **k: None):
|
||||
PysimHandler._do_POST(h)
|
||||
self.assertEqual(len(sent), 1, sent)
|
||||
return sent[0][0]
|
||||
|
||||
def test_a_cmd2_exception_is_not_a_successful_write(self):
|
||||
resp = self._write("EXCEPTION of type 'Exception' occurred with message: 'no card'\n")
|
||||
self.assertFalse(resp['success'])
|
||||
self.assertIn('EXCEPTION of type', resp['error'])
|
||||
self.assertNotIn('sw', resp)
|
||||
|
||||
def test_a_traceback_is_not_a_successful_write(self):
|
||||
resp = self._write('Traceback (most recent call last):\n File x\nValueError: boom\n')
|
||||
self.assertFalse(resp['success'])
|
||||
self.assertEqual(resp['error'], 'Traceback (most recent call last):')
|
||||
|
||||
def test_the_sw_semantics_are_unchanged(self):
|
||||
self.assertEqual(self._write('SW: 9000\n'), {'success': True, 'sw': '9000'})
|
||||
self.assertEqual(self._write('SW: 6982 - Security status not satisfied\n'
|
||||
'got 6982'),
|
||||
{'success': False, 'sw': '6982',
|
||||
'error': 'Security status not satisfied'})
|
||||
|
||||
|
||||
class ReadExceptionTests(unittest.TestCase):
|
||||
def _read(self, output, mode='raw'):
|
||||
app = FakeApp(output)
|
||||
body = {'name': 'EF.ADN', 'mode': mode, 'allow_probe': True, 'record_nr': 1}
|
||||
h, sent = _fake_handler('/api/read', body, app)
|
||||
with mock.patch.object(srv, '_select_with_parent', lambda *a, **k: (None, None)), \
|
||||
mock.patch.object(srv, '_get_file_type', lambda *a: 'transparent'), \
|
||||
mock.patch.object(srv, '_collect_apdu_times', lambda: None), \
|
||||
mock.patch.object(srv, '_end_apdu_time_collection', lambda: []):
|
||||
PysimHandler._do_POST(h)
|
||||
self.assertEqual(len(sent), 1, sent)
|
||||
return sent[0][0]
|
||||
|
||||
def test_a_cmd2_exception_is_not_a_successful_read(self):
|
||||
resp = self._read("EXCEPTION of type 'Exception' occurred with message: 'no card'\n")
|
||||
self.assertFalse(resp['success'])
|
||||
self.assertIn('EXCEPTION of type', resp['error'])
|
||||
resp = self._read('Traceback (most recent call last):\n File x\n', mode='decoded')
|
||||
self.assertFalse(resp['success'])
|
||||
self.assertEqual(resp['error'], 'Traceback (most recent call last):')
|
||||
|
||||
def test_a_normal_read_keeps_the_sw_parse(self):
|
||||
resp = self._read('AABB\nSW: 9000\n')
|
||||
self.assertTrue(resp['success'])
|
||||
self.assertEqual(resp['sw'], '9000')
|
||||
self.assertEqual(resp['data'], 'AABB')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user