v3.31.0: the EF_PBR (phonebook reference) support

EF_PBR ('4F30', TS 31.102 4.4.2.1) lists the phonebook's files as
BER-TLVs, and those files are not in pySim's model - so they were
invisible in the file tree.

- The PWA gains the decoder (efDecPbr, registered as EF.PBR/4f30): the
  'A8'/'A9'/'AA' type-1/2/3 containers, the 'C0'-'CC' kinds, the 02/03
  value lengths (FID / FID+SFI) and the master EF (the first FID after
  the first 'A8'), tolerant of FF padding, truncation, unknown tags and
  the spec's vague 'AA' path form (kept raw + noted, never a throw).
- The server reads the three PBR copies once per card session
  (best-effort, exactly like EF.ARR) into server.pbr and serves the raw
  records from the card-free GET /api/pbr.
- The file tree is populated SERVER-SIDE (no frontend involvement): a
  small Python parser reads the cached records and /api/tree appends the
  referenced files as children of the DF that holds the PBR
  ({'name','fid','isDir':false,'pbr':true,'sfi'}, deduplicated against
  the model), and /api/tree|select|read|write auto-allow the probe for
  exactly those FIDs, so the injected nodes are clickable without any
  PWA flag.  The node-to-PBR match is by the requested node's normalised
  path: a leading MF before an ADF segment is dropped and an ADF
  addressed by AID (the Profiler's walk) folds onto its name, so both
  DF_PHONEBOOK copies resolve to their own PBR; a full path never falls
  back to another DF's copy, and an ambiguous bare 5F3A injects nothing.
- Drift guard: the JS and Python parsers share
  frontend/tests/pbr_vectors.json, which includes the TS 31.102 Annex G
  record (its printed A8 length is internally inconsistent - 10
  primitives x 5 bytes = 0x32, not the printed 0x2D - so the corrected
  length is used, noted in the case name).

Docs: api.md (/api/pbr, the tree children, the auto-probe, the AA path
form), the repo AGENTS.  953 frontend + 989 Python; sw simple-v455.
This commit is contained in:
2026-10-18 01:47:04 +03:00
parent 30a742618e
commit 9ae9c39e55
9 changed files with 1237 additions and 10 deletions
+60 -1
View File
@@ -25,7 +25,7 @@ a 3.x PWA).
| `/api/command` | POST | pySim command (equip, status, tree, etc.) |
| `/api/commands` | GET | List available pySim commands |
| `/api/cardinfo` | GET | pySim `cardinfo` output |
| `/api/tree` | POST | File tree browser for given FID/name |
| `/api/tree` | POST | File tree browser for given FID/name (the DF holding EF_PBR gains the PBR-referenced files) |
| `/api/select` | POST | Select a file by name or FID |
| `/api/read` | POST | Read file content |
| `/api/write` | POST | Write raw hex data to a file |
@@ -80,6 +80,7 @@ a 3.x PWA).
| `/api/net-state` | GET | Cached network-state monitor (service, location, per-file state) |
| `/api/net-state-refresh` | POST | Re-read the monitored files (optional `files` list) and return the updated monitor state |
| `/api/arr` | GET | Cached EF.ARR copies (the FCP '8B' security attribute references): records per ARR file |
| `/api/pbr` | GET | Cached EF.PBR phonebook reference records (the raw TS 31.102 4.4.2.1 structure) |
| `/api/mcc-mnc` | GET | Search the optional MCC/MNC operator list (`?q=`; `?random=1&exclude=`) |
| `/api/proactive-log` | GET | Last 50 proactive commands |
| `/api/status-poll` | POST | Manual STATUS poll + FETCH if 91XX |
@@ -1457,6 +1458,34 @@ Returns:
{"exists": true, "name": "MF", "fid": "3F00", "file_type": "df", "children": [{"name": "EF.ICCID", "fid": "2fe2", "isDir": false}]}
```
**PBR-derived children**: when the requested node is the DF that holds the
card's **EF_PBR** (`4F30`, TS 31.102 §4.4.2.1 — `MF/7F10/5F3A`,
`ADF.USIM/5F3A` or `ADF.USIM-NON-IMSI/5F3A`), the server parses the cached
PBR records (see `GET /api/pbr`) and appends the phonebook files the PBR
references as children — the files are not in pySim's model, so they would
otherwise be invisible:
```json
{"name": "EF.ADN", "fid": "4F3A", "isDir": false, "pbr": true, "sfi": "02"}
```
`fid` is uppercase; `pbr: true` marks a PBR-derived node and `sfi` carries
the file's short file identifier when the PBR declares one (`null`
otherwise). A file pySim's model already lists is not added twice, and the
matching is by the requested node's full normalised path (uppercase, `3F00`
→ `MF`); a bare DF FID that cannot be told apart (the three PBR copies all
sit in a DF with FID `5F3A`) injects nothing. The PWA renders these nodes
like any other tree node — no frontend change.
Because the PBR files are not in the model, `POST /api/tree`, `/api/select`,
`/api/read` and `/api/write` **auto-allow the probe** for exactly the FIDs
the parsed PBR references (the PWA only sets `allow_probe` for its own custom
files), so the injected nodes can be selected and read without any client
flag. The probe stays scoped to those FIDs and the temporary model file is
detached again after the request. An explicit `allow_probe: false` does not
disable it — the server knows the file exists, so a caller cannot make the
tree's own nodes unselectable (the flag remains the PWA's custom-file switch).
### `GET /api/events`
Returns the event list captured from the card's SET UP EVENT LIST (an array of
@@ -1598,6 +1627,36 @@ FCI against this cache and shows the referenced record's access rules as
indented lines; snapshots resolve the same references from their own
captured `EF.ARR` copy.
### `GET /api/pbr`
Returns the card's cached **EF.PBR records** — the phonebook reference file
(`4F30`, TS 31.102 §4.4.2.1), whose BER-TLV structure describes the
phonebook files (the `'A8'`/`'A9'`/`'AA'` containers and the primitive
phonebook kinds). The copies are read once per card session when a card is
equipped (`MF/7F10/5F3A/4F30`, `ADF.USIM/5F3A/4F30`,
`ADF.USIM-NON-IMSI/5F3A/4F30`, best effort, at most `PBR_RECORD_LIMIT` = 16
records each) and served from memory, so this endpoint performs no card I/O;
a disconnect clears the cache.
```json
{"ok": true, "session": 12, "pbrs": [
{"fid": "4F30", "path": "MF/7F10/5F3A/4F30", "record_len": 32,
"num_records": 1, "records": ["A80AC0034F3A02C5034F0904AA0A..."]}]}
```
`ok` is `false` (with an empty `pbrs`) when the card has no readable PBR.
The records are served raw: the PWA owns the decoder for the decoded file
views (`efDecPbr`, TS 31.102 §4.4.2.1 — the containers, the primitive tags,
the master EF and the EF_IAP reference). The spec leaves the `'AA'`
container's "path to the file" form unspecified; a container that does not
parse as primitive tags is kept raw (the decoder notes it) and its files are
neither resolved nor injected into the tree — only same-directory FIDs are. The server keeps a small parser of
the same structure only to populate the file tree — `/api/tree` appends the
referenced phonebook files as children of the DF that holds the PBR and
auto-allows the probe for exactly those FIDs (see the `/api/tree` section).
The two parsers share the decode vectors in `frontend/tests/pbr_vectors.json`
(drift-guarded from both suites).
### `GET /api/proactive-log`
Returns the last 50 proactive commands fetched during CAT sessions, newest