feat: card presets move to a server-side store; the server owns the SCP80 counter (v3.8.0)
The presets lived in the browser's localStorage and the PWA wrote the SCP80 counter back after every operation, so an increment could be lost when the response never reached the tab, when two browser contexts (or the demo origin plus a local one) held separate copies, or when the tab was closed mid-run. The counter is card state and the server performs every card operation, so the store now lives next to the server: - pysim_simple_server/presets.py: one JSON file (~/.pysim-simple-server/card_presets.json, --card-presets overrides; Path.home() resolves the same way on Windows), atomic writes (temp + fsync + os.replace), an RLock, stable uuid ids, the ICCID normalisation the PWA uses (digits / spaced / raw EF hex), a duplicate-ICCID refusal and a monotonic set_counter; every counter change is appended to card_presets.json.audit.jsonl. - API: GET /api/presets (card-free), POST /api/presets and /api/presets/update|delete|import; the import also takes the old localStorage export shape - that is the migration path, there is no automatic migration. - Operations name a preset_id and the server persists the counter they consumed: /api/send-ota (the plain SCP80 path reports final_cntr now too), the RAM install/install-app/compat steps (_ram_send_gp_apdu) and the test runner's SCP80 steps (_preset_counter_persist). - PWA: the Cards tab is a thin client of the store (load on connect, CRUD via the API, export/import), the counter write-backs became local display syncs (the SCP80 form's counter input stays a deliberate human edit) and the SCP80/RAM bodies carry preset_id; a new pysimFetchOk() surfaces the server's error bodies instead of silently using them. Policy: card keys in logs and local files are not a risk for this workbench tool (AGENTS.md) - the store is not encrypted and no redaction is required. Tests: tests/test_presets.py (store: ICCID forms, counter monotonicity and wrap, atomic writes, corrupt-file recovery, import), the preset endpoints in tests/test_fetch_routes.py (incl. the counter persistence via /api/send-ota) and the updated PWA tests. sw cache simple-v334.
This commit is contained in:
+34
@@ -31,6 +31,11 @@ a 3.x PWA).
|
||||
| `/api/write` | POST | Write raw hex data to a file |
|
||||
| `/api/apdu` | POST | Raw APDU send |
|
||||
| `/api/verify-adm` | POST | Verify the card's ADM PIN (from the matched card preset) |
|
||||
| `/api/presets` | GET | Card preset store: all presets + the store path |
|
||||
| `/api/presets` | POST | Create a card preset (server-side store) |
|
||||
| `/api/presets/update` | POST | Update a preset (`{id, fields}`; the counter may be raised) |
|
||||
| `/api/presets/delete` | POST | Delete a preset (`{id}`) |
|
||||
| `/api/presets/import` | POST | Import presets (`{presets: [...], mode: merge\|replace}`) |
|
||||
| `/api/esim/chip` | GET | eUICC chip details (EID, EUICCInfo1/2, configured addresses) |
|
||||
| `/api/esim/profiles` | GET | Installed eSIM profiles with their metadata |
|
||||
| `/api/esim/notifications` | GET | Pending eSIM notifications (read-only) |
|
||||
@@ -183,6 +188,35 @@ On a wrong key (`63Cx`, x attempts left):
|
||||
A blocked ADM (`6983`/`9804`) reports `{"ok": false, "sw": "9804",
|
||||
"blocked": true}` and cannot be recovered without the card's unblock key.
|
||||
|
||||
### Card presets (server-side store)
|
||||
|
||||
Card presets (KIc/KID + keys, SPI, per-target TARs, counter, PSK pair, ADM)
|
||||
live on the server in `~/.pysim-simple-server/card_presets.json`
|
||||
(`--card-presets PATH` overrides it; `Path.home()` resolves the same way on
|
||||
Linux, macOS and Windows). The file is written atomically and every counter
|
||||
change is appended to `card_presets.json.audit.jsonl`.
|
||||
|
||||
The store is the source of truth for the SCP80 counter: the operations below
|
||||
accept a `preset_id` and **persist the counter they consumed themselves**
|
||||
(monotonic - a stale value never regresses it), so a closed tab, a lost
|
||||
response or a second browser window can no longer lose an increment. The
|
||||
Cards tab's export/import uses this API; the import also accepts presets
|
||||
exported by the older localStorage-based builds (the store assigns ids and
|
||||
applies the form defaults).
|
||||
|
||||
- `GET /api/presets` — `{"path": "…", "count": 2, "version": 1, "presets": [{…}]}`
|
||||
(card-free: it answers while a long card operation runs).
|
||||
- `POST /api/presets` — body = the preset fields; returns `{"ok": true, "preset": {…}}`.
|
||||
A duplicate ICCID (digits, spaced or raw EF hex are normalised to one form)
|
||||
is refused with `400 {"error": "card with this ICCID already exists: …"}`.
|
||||
- `POST /api/presets/update` — `{"id": "…", "fields": {…}}` (partial update;
|
||||
a plain `{id, name, …}` body works too). The counter is written as given -
|
||||
this is the deliberate human edit; unknown ids answer `404`.
|
||||
- `POST /api/presets/delete` — `{"id": "…"}` → `{"ok": true, "removed": true}`.
|
||||
- `POST /api/presets/import` — `{"presets": […], "mode": "merge"}` (or
|
||||
`"replace"`) → `{"ok": true, "added": N, "skipped": M, "errors": […]}`;
|
||||
invalid entries and duplicates are skipped and reported, never fatal.
|
||||
|
||||
### eSIM / LPA (local ES10 operations)
|
||||
|
||||
These endpoints work only when the equipped card is an eUICC (SGP.22/32);
|
||||
|
||||
Reference in New Issue
Block a user